Interface TaskExecutorExtension
- All Superinterfaces:
HostExtensionPoint
An implementation is an ordinary Spring bean in the bundle's plugin context (see the
X-Plugin-Context manifest header). Nothing here is OSGi-specific: no
BundleActivator, no BundleContext, no Declarative Services annotations. The
host discovers implementations through PluginExtensionRegistry and never registers them
into the root ApplicationContext.
A complete task type is two things:
- a declaration in the bundle's
type-definitions.xml, of a type extendingxlrelease.PluginTaskConfiguration, which defines the properties a release author fills in; and - one bean implementing this interface, whose
getTaskType()returns that declared type's name.
Execution model
execute runs on a host worker thread, not on the caller's thread, and it may
block. It is invoked from the same bounded pool that runs every other task job in the
product, so a blocking call holds one of those slots for its duration - exactly as a Jython
script does today. That is why a plain synchronous signature is acceptable on top of an
asynchronous host.
Block only for short, bounded waits. A single request/response is fine;
seconds, not minutes. Anything open-ended - waiting for a deployment, an approval, a remote job -
must return TaskExecutionResult.suspendFor(java.time.Duration) or
TaskExecutionResult.awaitSignal() instead of sleeping. This is not a style preference:
a parked thread is heap state. It does not survive a restart, redeploy, crash or failover,
whereas a suspended task is persisted and does. Blocking for a long wait would silently lose
in-flight work on restart, and would pass testing, because short waits work fine.
Honour Thread.interrupt() and return promptly. Interruption is how
the host aborts a running task. An implementation that swallows interrupts cannot be aborted.
execute may be entered more than once for the same logical step.
Job delivery is at-least-once, so implementations must tolerate re-entry - make external calls
idempotent, or record progress in output properties and check it on entry (see
TaskExecutionContext.isResume()).
Threads you start are yours. You may create threads, including virtual
threads, inside execute; the host neither provides nor bounds them, and does not stop
them at teardown. Anything you start must be joined or completed before execute returns.
A thread that outlives the call is untracked by the host and keeps your bundle's classloader
alive, which prevents your plugin from being cleanly uninstalled. Note also that on JDK 21,
blocking inside a synchronized block on a virtual thread pins its carrier thread and
costs throughput; prefer java.util.concurrent.locks around I/O.
A thread you start does not inherit the release's authenticated identity.
execute itself runs already authenticated as the release's script user (the same
identity, resolved the same way, that a Jython task's script runs as - see the release's "Run
automated tasks as user" property), so a bridged host API call made directly inside
execute is authorized correctly with no extra work. That identity lives in a plain, non-
inheritable ThreadLocal, though, so a thread you spawn does not carry it: a
bridged API call made from that thread runs with whatever ambient identity the new thread
happens to have - typically none - and fails authentication rather than silently running as the
wrong user. Make bridged API calls directly on the execute thread, or hand results back
to it and make the call there.
Failure
Return TaskExecutionResult.failed(String) for an expected failure with a message a
release author can act on. A thrown exception is also treated as a failure, but the author sees
a stack trace rather than an explanation, so prefer the explicit result.
- See Also:
-
Method Summary
Modifier and TypeMethodDescriptionexecute(TaskExecutionContext context) Runs the task.The name of the configuration type this extension implements, inprefix.Nameform - for example"acme.HelloTask"- exactly as declared in the bundle'stype-definitions.xmland as rendered bycom.xebialabs.deployit.plugin.api.reflect.Type#toString().default voidonAbort(TaskAbortContext context) Called after a task of this type is aborted while a previousexecutehad left it waiting (TaskExecutionResult.suspendFor(java.time.Duration)/TaskExecutionResult.awaitSignal()), so the extension can release external resources — cancel a delegated remote job, close a reservation — that would otherwise be orphaned, sinceexecuteis never re-entered after an abort.
-
Method Details
-
getTaskType
String getTaskType()The name of the configuration type this extension implements, inprefix.Nameform - for example"acme.HelloTask"- exactly as declared in the bundle'stype-definitions.xmland as rendered bycom.xebialabs.deployit.plugin.api.reflect.Type#toString().This is the host's dispatch key: it is matched against the configuration type of the task being executed. It must be unique across all installed plugins; a duplicate is a plugin installation error, not a silent override.
Note that this is the configuration type the author declared, not
task.getType()- which isxlrelease.PluginTaskfor every plugin task, since one compiled task class backs them all.- Returns:
- the declared configuration type name; never
nullor blank
-
execute
Runs the task.Called on a host worker thread. See the class Javadoc for the blocking, interruption, idempotency and thread-ownership rules that apply here - they are contractual, not advisory.
- Parameters:
context- access to the task's properties, identity and execution log; nevernull- Returns:
- what the host should do next; never
null - Throws:
Exception- treated as a task failure; preferTaskExecutionResult.failed(String)for expected failures
-
onAbort
Called after a task of this type is aborted while a previousexecutehad left it waiting (TaskExecutionResult.suspendFor(java.time.Duration)/TaskExecutionResult.awaitSignal()), so the extension can release external resources — cancel a delegated remote job, close a reservation — that would otherwise be orphaned, sinceexecuteis never re-entered after an abort.Best-effort, after the fact. The task is already failed when this runs; nothing returned or thrown here changes that. Runs asynchronously on a host executor with the same thread context as
execute(bundle TCCL, release script user), so bridged host API calls work. The same rules apply: keep it to short bounded calls, honour interruption, join any threads you start. A thrown exception is logged and swallowed.Idempotency is contractual here too. Delivery is at-least-once (the host dedupes best-effort, not transactionally), and an abort may race a still-running
execute— in which case the output properties read through the context may predate that execution's unpersisted writes. Release external resources idempotently, and treat an absent correlation id as "nothing to release", never as an error.The default does nothing — implement only if the task holds external state worth releasing.
- Parameters:
context- read-only view of the aborted task's properties and identity- Throws:
Exception
-