solo 0.2.0
solo: ^0.2.0 copied to clipboard
One job at a time: sequential jobs with exclusive state ownership, declarative rules and cooperative cancellation. Pure Dart.
0.2.0 #
The first published release. 0.1.0 never left the tree, so nothing below is a migration anybody has to make; what changed since it is at the end, for a tree that followed the package before it went out.
-
Add
onErrorandonCancelstate handlers torunandjob. They apply state after children and cleanup, before the next queued job, while preserving the failed or cancelled outcome. Incompatible external state changes suppress correction, including during cleanup and for descendants. The quick start now uses sealed loading/result/error states. -
Add
AccumulationTiming.debounceand.throttletocollectandaccumulate. Debounce seals a group after a pause between events; throttle spaces actual group starts. Waiting groups let ready jobs pass, while handlers, children and cleanup remain sequential.collectretains every event;accumulateretains themergeresult. Closing cancels timing timers and queued groups without a final batch. -
Breaking:
ctx.run(child)returns the child's result asFuture<T>. Replaceawait ctx.run(child).valuewithawait ctx.run(child); keep the original child handle to cancel it or inspect its outcome. After success,runchecks the parent's cancellation and state rules before returning. Concurrent starts must handle or explicitly ignore the returned future. Controllerrunandctx.eachstill return job handles immediately. -
Add
collectandaccumulatefactories returningSoloAccumulator: gather events in a list or merge them into one value before execution.AccumulationPolicyselects adjacent grouping, replacement at the queue's tail with data transfer, or joining an existing queued group. Groups use ordinarySoloJoblifecycle, state rules and cancellation. -
Re-export
Job.thenandChainCancelReasonfrom the core. Continuations support cancellation chains and have their ownJobContext; they do not inherit controller state, rules, observers or a queue slot. -
SoloBase<S>engine: one root job at a time, exclusive state ownership. -
Solo<S>with a broadcaststream, delivered on the next microtask. -
Jobs as
asyncbodies with a working typeW,canStartandkeepWhilerules,cancellable: false. -
Keep a replacement cancelled by a synchronous listener out of the queue, so it cannot absorb a later
droppablejob. -
Requires
async_job: ^0.2.0:job.whenCancelled(callback)registers a synchronous listener receivingCancelledand returns an unregister function. -
Job<T>handle:done,value,outcome,whenCancelled,cancel,ignore. -
Outcome<T>:Done,Failed,CancelledwithCancelReason. -
A
Failedoutcome nobody observed goes to the zone that created the job, the way Dart reports an unhandledFutureerror. Readingdoneorvalue, or callingignore, counts as observing it;Cancellednever reaches the zone. -
JobContextof the kernel:check,wait,join,uncancellable,unattended,onCancel,run,each,log. -
SoloContext<S, W>on top of it, where the state lives:state,stateAs,emit. -
A body does not
awaiton its own: every call goes through the context, and the member it picks says what a cancellation does to that call.waitends the waiting and lets the action run on;joinwaits for all of the action and gives up afterwards, so a device call is never left mid-flight;uncancellableholds the cancellation for the length of the call and gives up once the step is over;unattendeddoes not wait at all and hands the work to the engine, which hears it fail even after the job is over. -
ctx.unattended(action)for work a body starts and does not wait for. It runs in an error zone of its own, and whatever it leaves uncaught — now, or long after the job is over — reachesonErrorinstead of the process. -
Behaviour change. With no
SoloBase.observerand no override ofSoloBase.onError, an error with nowhere else to go no longer stops in the empty hook: it goes to the zone the job was created in, the way the core reports one when a job has no observer. That covers a disposer, anonCancelcallback, a late failure of an abandoned call or of unattended work, and acanStartorkeepWhilethat threw instead of answering while the job runs. ACancelledis the one exception and never goes there. Install an observer, or override the hook, and the route is yours again; callsuper.onError(...)from the override to keep it. A rule that throws before the job starts is not on this route at all: it becomes aFailedoutcome and reaches the zone as any unobserved failure does, whatever the observer. -
A cleanup stack instead of a
finallyin the body:ctx.onDisposereleases whatever the outcome,ctx.onDiscardonly when the value reaches nobody, andwaitandjointake the same two asdisposeanddiscardfor the value they hand over — a connection or a file an abandoned action opened is still closed. The engine unwinds the stack after the children and before the outcome, andclosewaits for it;ctx.disown(value)takes a registration back when the body hands the value over itself. -
JobContext.uncancellableiswait's counterpart: it runs a step that cannot be taken back — a payment on its way to the server — with the cancellation held until the step is over, andclosewaits for it. -
Breaking:
ctx.each(stream, (child, event) { ... })returns a childJob<void>immediately. Await.valuefor a throwing result, inspect.done, or cancel the subscription separately through.cancel(). The callback receives aSoloContext<S, W>retaining the parent's working type andkeepWhile, without inheritingcanStart. The parent waits for this child even without an explicit await, and observers see the child. Parent cancellation cascades to it. -
Cancelling
eachremoves the subscription immediately, then waits for the running callback before child completion and cleanup. Use child context checkpoints to respond to cancellation; a plainawaitcan delay cancellation andclose(). Source cleanup from subscription cancellation is still not awaited.eachis now a context method and followsrun's restrictions on when and where children can start. -
JobContext.onCancelhands a cancellation to something that can really stop — a device's cancel token, an HTTP abort.waitends the waiting, not the work. -
Child jobs via
ctx.run; a parent finishes after its children. -
SoloQueuewithremove,removeWhere,clear,lastWhere; policiessequential,droppable,replace,restart. -
externalSetStatefor hardware listeners; every change re-evaluates the rules of running jobs. -
SoloObserverand instance hooks;SoloBase.debugengine tracing. -
A hook that throws changes nothing: the engine hands its error to the current zone and carries on, and the hook next to it is still called.
Since 0.1.0 #
- The job kernel now lives in
package:async_joband is re-exported whole:package:solo/solo.dartstays the only import. Code using the old reason API must migrate to the reason classes. The job's lifecycle, context, outcomes and observer moved to the kernel; the state, queue and rules stay insolo. CancelReasonis an extensible class hierarchy:ManualCancelReason,ParentCancelReasonandHandlerCancelReasonfrom the core, plusRulesCancelReasonandClosedCancelReasonfromsolo. Inspect types;nameis only a log label, with no equality by name. Custom subclasses can carry arbitrary data throughjob.cancel(reason: reason). Parent and child propagation retain the original cancellation incause.Cancelled.by({reason, started, description, stackTrace})is public: an engine of a domain builds cancellations with a reason of its own.JobContext<S, W>is renamed toSoloContext<S, W>, and the nameJobContextnow belongs to the interface of the kernel: cancellation, the waiting family and children, without the state.SoloContextimplements it and addsstate,stateAsandemit. Job bodies do not write the name, so code written against the README keeps compiling.- The job and its context are split along the seam the kernel will be
taken out at:
JobBase<T>andJobContextBasecarry the lifecycle, the waiting family, the children and the outcome, and the solo subclasses add the state, the rules and the queue. Nothing moves in the public API of a controller. - The list of children a job waits for now shrinks as they finish: a
long-lived job starting children in a loop no longer grows one entry per
child. The description of a parent's own outcome is unchanged — the link
from an outcome to the child that carried it lives in an
Expando, so a child without a key still shows in it. - A value a body returned after its cancellation had already arrived is
released rather than dropped: the body registers it on the cleanup
stack, the outcome stays the cancellation, and
closewaits for the release. - A job says who may adopt it:
ctx.run(child)refuses a job of another controller withArgumentErrorand a job still waiting in the queue withStateError, and a job ofsolois refused by a context of the bare kernel. A child without an observer of its own inherits the parent's. JobBase.debugtraces the life of a job,SoloBase.debugthe queue, the state and the closing. Set both to follow both.JobObserveris the observer of a single job:onStart,onFinish,onErrorandonLog, without the controller in the signatures. A controller feeds its ownSoloObserverand its instance hooks from it, so nothing changes for a user ofsolo.isQueuedmoves fromJob<T>to the newSoloJob<T>, returned byjob,addandrun. The queue is the controller's, not the job's.JobStatusis public and has three values: a queued job iscreateduntil it starts, andisQueuedis computed from the queue itself — insidecanStarta job taken from the queue is no longer queued.
0.1.0 #
Never published.