Skip to content

Reject invalid Go SDK task handler registrations up front - #73549

Merged
henry3260 merged 3 commits into
apache:mainfrom
henry3260:go-sdk-reject-invalid-registrations
Sep 23, 2026
Merged

henry3260 merged 3 commits into
apache:mainfrom
henry3260:go-sdk-reject-invalid-registrations

Conversation

@henry3260

@henry3260 henry3260 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Toward "make every registration-time mistake fail at registration rather than on first execution". Checked against the scope, main already satisfies two of its four points, so this PR closes the gap in the other two.

Why

Registration did not close at Serve. ADR 0007 states that it does and that a later Register is a programming error that panics, but nothing enforced it. BundleRef carried no such state, so a Register left below Serve in main silently added a handler to the map the runtime was already answering from — what the bundle could run depended on how far main had got.

A variadic task function registered cleanly and then panicked on first execution. func(actx airflow.Context, names ...string) error passed the signature check as if its ... parameter were an ordinary []string. Execute calls the function with Call, which hands a variadic parameter the slice itself instead of spreading it, so the first run under the supervisor panicked inside reflect. No argument shape can fill a ... parameter on this path, so the registration can never become valid; []string takes the same values and works.

What

  • go-sdk/airflow/bundle.go — taskHandlerMap gains a closed field and a close method. The check sits inside add, under the mutex that already guards the map, so there is no window between reading the flag and writing the handler.
  • go-sdk/airflow/serve.go — serve closes registration on entry, before parsing flags, so it also applies to a run that only prints usage or fails on a flag. The mistake is where Register sits in main, not what that run turned out to do.
  • go-sdk/internal/bundle/task.go — validateFn rejects a variadic fn, placed after the Kind check that reflect.Type.IsVariadic requires.
  • Doc comments on Register and Serve now state that registration closes.
  • Tests: TestRegisterAfterServePanics, TestRegisterAfterAFailedServePanics, and a variadic case in the existing TestTaskHandlerPanicsOnBadHandler table. go test ./... -count=1 and go test ./airflow/ -race pass; the concurrent-registration test still passes with the new field.

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 5)

@henry3260
henry3260 marked this pull request as ready for review September 22, 2026 13:21

@jason810496 jason810496 left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/orgs/apache/projects/499/views/7?pane=issue&itemId=248986989

  • Registering after Serve has been called panics - registration closes when serving starts.
  • A duplicate (dagId, taskId) panics.
  • A struct carrying arg: tags mixed with other data parameters is rejected (an untagged struct has no such guard and is decoded positionally as one value).
  • Signature errors - non-function, wrong return shape, missing leading airflow.Context - panic with the offending function named.

It seems the scope of current PR is not align with the issue scope.
How about checking whether all the above issue scope are covered instead of having current refactor.

Additionally, the task id validation should be in compile time instead of at the task runtime. IIUC, the current refactor will introduce side-effect (might raise validation error) during the runtime.

@henry3260
henry3260 marked this pull request as draft September 22, 2026 13:54
A variadic function passed the signature check as if its ... parameter were
an ordinary slice, so it registered cleanly and then panicked inside reflect
the first time the supervisor ran it: Execute calls the function with Call,
which hands a variadic parameter the slice itself instead of spreading it.

There is no argument shape the runtime can bind to a ... parameter, so the
registration cannot become valid later.
ADR 0007 states that registration closes when Serve is called, but nothing
enforced it: a Register left below Serve in main silently added a handler to
the map the runtime was already answering from, so what the bundle could run
depended on how far main had got.

Serve closes registration whatever the run goes on to do, since the mistake
is where Register sits in main rather than what that run turned out to be.
@henry3260
henry3260 force-pushed the go-sdk-reject-invalid-registrations branch from 9a69eae to 2e058b5 Compare September 22, 2026 14:14
@henry3260

Copy link
Copy Markdown
Contributor Author

Go-SDK (view)

  • Registering after Serve has been called panics - registration closes when serving starts.
  • A duplicate (dagId, taskId) panics.
  • A struct carrying arg: tags mixed with other data parameters is rejected (an untagged struct has no such guard and is decoded positionally as one value).
  • Signature errors - non-function, wrong return shape, missing leading airflow.Context - panic with the offending function named.

It seems the scope of current PR is not align with the issue scope. How about checking whether all the above issue scope are covered instead of having current refactor.

Additionally, the task id validation should be in compile time instead of at the task runtime. IIUC, the current refactor will introduce side-effect (might raise validation error) during the runtime.

Thanks I had misunderstood earlier.
Implemented the first point instead: registration now closes in Serve, and a later Register panics. It was the only one of the four main did not already satisfy.
I kept the variadic check as a signature error of the same class: Executecalls the handler with Call, which cannot spread into a ... parameter, so it registers cleanly and panics inside reflect on first run. It sits in validateFn beside the existing checks.

@henry3260
henry3260 marked this pull request as ready for review September 22, 2026 15:47

@jason810496 jason810496 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks for the update.

Comment thread go-sdk/airflow/bundle.go Outdated
The flag describes whether the bundle still takes registrations, not the
state of one of the maps it holds. Keeping it on the handler map meant every
further kind of registration would need a flag of its own and a matching line
in Serve, so a native Dag would have arrived with the check missing until
someone remembered it.

The check now runs before Register looks at what the item is, so it covers a
kind added later for free. It reads an atomic rather than taking a lock
because the handler map keeps its own — the runtime is handed that map on its
own — and a second mutex would introduce a lock order to respect.

@jason810496 jason810496 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. LGTM now.

@henry3260
henry3260 merged commit 21b83f4 into apache:main Sep 23, 2026
89 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants