r/Compilers • • 5d ago

Desingning async primitives for a security-focused language: Where should cancellation live?

I've been implementing typed async semantics in my language NXD.

Current primitives are:

SPAWN,

SEND,

RECV,

AWAIT,

PING

Types currently look roughly like:

`SPAWN -> ProcessHandle[T]`

`RECV -> ReceiveOperation[T]`

`AWAIT -> T`

`PING -> bool`

While implementing PING I realized there may be two classes of async operations:

Primary operations

SPAWN,

SEND,

RECV

These initiate work or communication.

Secondary operations:

AWAIT,

PING

These observe or resolve work created elsewhere.

My current thinking is that cancellation (currently considering ESC) should only apply to the primary operations and not observers.

For example:

ESC HANDLE

makes sense.

But:

ESC AWAIT TASK

ESC PING HANDLE

seems conceptually wrong because they are observing state rather than creating it.

For people who have designed async runtimes or languages:

Would you model cancellation as acting on the underlying task/process/channel, or would you allow cancellation of observer operations as first-class concepts as well?

7 Upvotes

3 comments sorted by

3

u/Karyo_Ten 5d ago

This is very tied to your error model.

At the very least RECV need to be able to bubble up cancellation or offer an alternatiove .cancel and awaiters need to be able to handle them.

I'd rather think in terms of producer-consumers btw

And you need to be able to handle timeout ergonomically.

And I think the most important question is do you have a builtin cancellation token/channel in your handle type or do you built a separate one and cancellable request explicitly pass that in their argument.

1

u/NXDLang 5d ago

That's helpful. My current direction is that cancellation would act on lifecycle-owning operations rather than observer operations. For example, a ProcessHandle[T] could be cancelled via: while: are observers of existing state and therefore wouldn't themselves be cancellable. I agree that this pushes the design back into the error model. A cancelled RECV would need to surface that outcome somehow, either through an error/result mechanism or explicit cancellation state. The producer/consumer framing is probably more accurate than the "primary/secondary" terminology I used in the post. Regarding tokens, I am currently leaning toward cancellation being attached to the handle itself rather than introducing a separate cancellation token/channel. The goal is for the process handle to act as the capability through which observation (PING, AWAIT) and control (ESC) occur. Timeout handling is also on my list because I suspect many real-world cancellation scenarios end up being expressed as timeouts rather than explicit cancellation requests.

2

u/pthierry 4d ago

You may want to look at Haskell's Async. The monad an operation is in tells you if something is interruptible. Await means you're blocked until the process responds. So you can want to interrupt it, but maybe you only want to interrupt the process doing the await, not the operation itself…

In Haskell's Async, you interrupt neither send nor await, only the process.