If you’ve spent time in the BC base app, you’ve probably discovered that there’s a codeunit object (ID = 490) called Parallel Session Management and in very short words it’s the object behind the classic on-prem pattern for fanning work out across multiple NST sessions.
What the codeunit does?
Codeunit 490 is a self-contained job scheduler, built entirely on [Scope('OnPrem')] procedures. There’s no async/await, no platform-managed queue, it’s a manual, in-memory orchestrator.
The three entry points all funnel into the same setup:
[Scope('OnPrem')]
procedure NewSessionRunCodeunit(CodeunitId: Integer; Parameter: Text): Boolean
begin
if not CreateNewPSEntry(CodeunitId, Parameter) then
exit(false);
StartNewSessions();
exit(true);
end;
NewSessionRunCodeunitWithRecord attaches a RecordID to the job; NewSessionRunCodeunitWithBlob goes a step further and hands a TempBlob to the child session using a Codeunit "Memory Mapped File" because sessions don’t share memory, so a blob has to cross that boundary through an OS-level shared memory segment instead.
Every queued job lives in a temporary record, TempParallelSessionEntry, tracked entirely in the calling session’s memory. StartNewSessions walks the unprocessed entries and calls the platform’s native StartSession, throttled against GetMaxNoOfSessions (default 10, hard-capped at 1000 — the size of the MemoryMappedFile array).
Liveness is polled, not event-driven:
local procedure RefreshActiveSessions()
...
if not ActiveSession.Get(ServiceInstanceId(), TempInteger.Number) then begin
TempInteger.Delete();
NoOfActiveSessions -= 1;
...
And if you want to block until everything’s done, WaitForAllToFinish will happily sit in a loop for up to an hour, sleeping 2 seconds at a time, showing a “Waiting for background tasks” dialog while it polls.
It’s a solid piece of engineering for what it was built for: a single, known NST instance, spinning up sibling sessions on itself, coordinating them by hand. Which is exactly the problem.
Why it’s not a good fit for SaaS?
The design of this codeunit is not a good fit for a shared cloud environment. Reasons are different:
1. The blocking wait pattern doesn’t fit a metered, multi-tenant runtime.
WaitForAllToFinish can occupy a session for up to 3600 seconds, doing nothing but sleeping and polling ActiveSession. On-prem, that’s a cost you choose to pay on your own hardware. In SaaS, sessions are a shared, governed resource with concurrency caps and timeout policies designed specifically to stop exactly this kind of long-lived, idle-but-occupied session. Letting this pattern in would mean fighting the platform’s own session governance with a manual scheduler that predates it.
2. Memory-mapped files are the wrong shape for a multi-tenant boundary.
NewSessionRunCodeunitWithBlob relies on OS-level shared memory to move data between sessions. That’s a reasonable trick on a single-tenant server you control. In a multi-tenant SaaS runtime, anything that touches raw process/OS memory sharing raises the isolation bar considerably.
To pass data between two sessions, this code has them both read and write to the same spot in the server’s memory, like leaving a note in a shared drawer. On your own private server, that’s fine. Nobody else uses that drawer. But in BC Online, one server holds many different customers at once. And the rule is: no customer should ever be able to reach into another customer’s stuff, even by accident. A “shared drawer” is risky in that setup — not because this code misuses it, but because sharing memory directly just isn’t a safe habit to allow when strangers share the same server.
It’s not that the current logic is unsafe; it’s that the primitive itself doesn’t belong in that trust boundary.
3. It only knows about one node and SaaS doesn’t work that way.
This is the one I’d lead with if I were explaining it to a room of BC developers, because it’s the most fundamental. StartSession spawns a new session on the same NST instance as the caller. On-prem, where you’re often running one or a handful of statically known instances, that’s a fair assumption. BC Online distributes a tenant’s sessions across multiple nodes for load balancing, with no guarantee that a newly spawned session lands anywhere near its parent.
Worse, the liveness check is explicitly node-scoped:
ActiveSession.Get(ServiceInstanceId(), TempInteger.Number)
ServiceInstanceId() identifies the current node. If the child session were scheduled on a different node — which in SaaS it very well could be — the parent’s polling loop would never see it as active. The entire bookkeeping model, the session-number pool in TempInteger, the “is it still running” check, all silently assume a single-node world that online environments simply don’t provide.
The takeaway?
Codeunit 490 encodes three assumptions — sessions are cheap to block on, memory can be shared across sessions, and everything runs on one node — that were all true for on-prem and are all false for SaaS. You can’t fix that with a permission flag; the pattern itself needs to be replaced.
Which is exactly what the platform has been doing: Job Queue entries and the newer async/task execution patterns were built node-agnostic and event-driven from the ground up, instead of polling a local session table. If you’re building anything today that needs “run this in the background, potentially several of these in parallel,” that’s where to look — not at codeunit 490 (and this is why you will never see this codeunit exposed for SaaS as is).

Hi Stefano
Could you explain a bit more Memory-mapped files are the wrong shape for a multi-tenant boundary issue?
I understood that the piece of code in C490 is about managing parallel background session in the same tenant.
Since CU490 uses StartSession() and StartSession() always spawn another session on the same NST (and the same tenant) there is newer a multi-tenant boundary.
The potential issue could be the memory pressure if the payload is enormous, but it would be still the same NST, potentially diffferent company but in the same tenant. And even then if you pass this via, say, AzureBLOB the other session will have to read content into own memory anyway, the net effect will be the same (as long as new session is on the same NST – which it is if managed via StartSession() and not JobQueueDo I get that right?Then since the StartSession() is bound to current session NST then there will be never a problem with ServiceInstanceId() returning different value.Or I am missing something?
Best
Slawek
LikeLike
This code has two sessions share the same piece of memory to pass data — like two people using the same notebook.
On your own private server, that’s fine — it’s just you.
But BC Online servers hold many different customers at the same time. And the rule is: one customer should never be able to peek into another’s notebook. So sharing memory directly is too risky to allow there, even if this particular code uses it safely.
LikeLike