Concurrency¶
The SDK follows A11's concurrency model and uses
its licensed thread primitives. Common Future/Task and channel headers are
shared by host and guest; each platform supplies its own primitive backend.
Use <symbian/concurrency/...> for completion and executor APIs, and
<thread/...> for fibers, synchronization and channels.
Futures and Tasks¶
Link Symbian::Stackless in a guest application. Promise<T> produces a
Future<T> carrying absl::StatusOr<T>; Task is Future<Unit>.
OnReadyandThenrun inline on the completing thread, or immediately when registered on a ready Future. Marshal UI changes to the event thread.JoinAllpreserves result order.DriveInlinebounds reentrant work.Cancel()requests producer action; it does not complete the Future itself. An abandoned incomplete Promise publishes a cancelled result.TaskGroup::Finish()completes after every child settles and reports the first error. Dropping an unfinished group requests cancellation without synchronously draining its native resources.- An unresolved guest
Awaitparks inside an SDK fiber and returnsFailedPreconditionoutside one. A ready Future can be read immediately.
Retain request buffers, statuses and handles until completion has drained. Cancellation requested does not mean ownership can be released.
Turn a timer into a reminder¶
Pass an already-open event-thread timer pump. The returned Future carries the reminder text or the timer's error; it does not block the event loop.
#include <string>
#include "symbian/concurrency/timer_pump.h"
symbian::concurrency::Future<std::string> ReminderAfter(
symbian::concurrency::TimerPump& timers) {
namespace tasks = symbian::concurrency;
return tasks::Then(timers.ScheduleAfter(absl::Seconds(30)),
[](const absl::StatusOr<tasks::Unit>& result)
-> absl::StatusOr<std::string> {
if (!result.ok()) {
return result.status();
}
return std::string("Time to check the oven");
});
}
Keep pumping native completions while the reminder is pending. Retain the
Future to observe the result or request cancellation; the pump must stay alive.
An OnReady observer may run immediately when attached to a completed Future.
Native request owners and the event loop¶
NativeTimer owns one thread-relative RTimer. Create, arm, cancel and close
it on the same OS thread. It rejects overlapping arms and drains a pending
request before close; the surrounding event loop owns the shared semaphore wait.
TimerPump turns timer completions into Tasks. ScheduleAfter accepts an
absl::Duration; ScheduleAt accepts an absolute absl::Time and converts it
once to monotonic waiting. Later wall-clock corrections do not move the request.
Negative delays fail with InvalidArgument; unrepresentable finite deadlines
fail with OutOfRange. Past deadlines complete on the next turn; infinite
future stays pending until cancellation or close. Long waits use native arms
of at most signed 32-bit microseconds.
The pump admits 64 pending timers and dispatches at most 64 completions per
turn by default. Saturation returns a ready Task with ResourceExhausted.
Workers may request cancellation; the event thread alone touches the timer and
publishes completion. DispatchReady may invoke callbacks inline.
PropertyWatch owns one integer Publish & Subscribe property and one change
request. Next() returns Future<int>. Open, subscribe, set, dispatch and
close on the event thread; worker cancellation coalesces a wakeup. The request
is removed before its callback, so that callback may subscribe again.
EventMailbox admits bounded cross-thread callbacks through a nonblocking
queue. Dispatch runs callbacks outside its lock and defers reentrant work to
a later turn. Its default admission limit is 128.
EventExecutor drives timer and property completions, the mailbox and guest
fibers on one event thread. It arms fiber deadlines through the same timer
owner. Window Server status ownership stays with the application: process
those completions before the single native request-semaphore wait.
NativeTaskOwner composes timer/property work with inherited cancellation and
an absolute deadline; its asynchronous join includes deadline-alarm drainage.
Workers¶
Use WorkerExecutor for blocking I/O and long computation.
EventExecutor::workers() lazily creates one shared worker for that event
owner. future.ThenOnWorker(event_executor, transform) copies the result and
runs the transformation there; Then and OnReady remain inline.
The lower-level ThenOn(future, worker, transform) selects a worker explicitly.
For example, count lines in a downloaded text result on a worker. Here
downloaded is a Future produced by your download operation, and worker is
an existing executor:
#include <algorithm>
#include <cstddef>
#include <string>
#include "symbian/concurrency/worker_executor.h"
symbian::concurrency::Future<std::size_t> CountDownloadedLines(
const symbian::concurrency::Future<std::string>& downloaded,
symbian::concurrency::WorkerExecutor& worker) {
return symbian::concurrency::ThenOn(
downloaded, worker,
[](const absl::StatusOr<std::string>& text)
-> absl::StatusOr<std::size_t> {
if (!text.ok()) {
return text.status();
}
if (text->empty()) {
return std::size_t{0};
}
return std::count(text->begin(), text->end(), '\n') +
(text->back() != '\n');
});
}
The download should already have a body-size limit. The worker must remain available through completion. This result's inline observers run on the worker; post any label update to the UI owner's mailbox.
PostFiber(work, stack_bytes) schedules a fiber on the worker's own scheduler.
The default stack is 16 KiB; accepted sizes are word-aligned from 4 KiB to
1 MiB. TLS handshakes can need 256 KiB. The admission cap includes queued and
active work; a full or closed executor returns a status.
Close() does not block the event thread. Finish() reports asynchronous
drainage. A job or fiber that waits forever can prevent drainage. The guest
has no preemption, shared Post/PostAt pool or complete A11 cancellation tree.
Fibers, synchronization and channels¶
Link Symbian::Fibers for guest thread::Fiber and thread::Scheduler.
Fibers and schedulers remain pinned to their creating OS thread. Keep both
alive until the fiber finishes; destroying unfinished ownership is a runtime
contract failure. Fibers do not virtualize OS TLS, heaps or Symbian leave state.
thread::Mutex, MutexLock, CondVar and SleepFor park fibers cooperatively.
Outside a fiber they use blocking OS-thread behavior, so keep those calls off
the event thread. CondVar follows A11's convention: true means timeout and
false means signal. SchedulerPolicy selects ready ordering and provides a
wake hook; EventExecutor supplies the native-wait integration.
thread::Channel<T> supports bounded buffering, rendezvous, selection,
cancellation-aware writes and one-time close. Case, PermanentEvent,
Select and SelectUntil share one selection protocol. Guest selection
rotates the first case; a finite wall deadline is converted once to monotonic
waiting. A closed-channel write is fatal in the no-exceptions guest profile.
Use symbian::concurrency::BoundedChannel<T> for nonblocking, fallible mailbox
operations and idempotent close.
Host backend and Python¶
The host archive bundles Boost.Fiber/Context behind a Boost-free public ABI;
consumers need no separate Boost libraries or headers. The host supplies
thread-affine fibers with cooperative cancellation, explicit join, C++ cleanup
and no detach or forced unwind. Shared-pool Post and PostAt run stackless
callbacks. Host unresolved Await parks and honors an absolute deadline.
The adaptation does not supply A11's full work-stealing pool, pooled fiber
Submit/Schedule, introspection or fiber tree.
Exceptions remain disabled by default. The Boost primitive and pool-teardown translation units explicitly enable them for their implementation requirements. Python bindings use A11's CPython park guard and deferred-reference holders: native waits release the GIL, Python access acquires it, and asyncio completion returns to the loop thread. Python policy stays outside native libraries.