DISCLAIMER: I diagnosed this with Claude, there may be errors, however, the problem is confirmed by myself running the test and in my main firmware.
On TinyCLR OS v3.0.0.2000-prerelease (SITCore SC20260), if any thread is parked inside a blocking Socket.Accept() (and by the same mechanism, a blocking Receive()), every Thread.Sleep wake-up in the entire application quantizes to ~250 ms boundaries (± one 20 ms scheduler quantum). 250 ms is the lwIP tcp_slowtmr period, which suggests timed wake-ups only get serviced on the network stack’s slow-timer cycle while a native socket wait is parked.
Running compute is unaffected (threads that have the CPU keep full speed), and interrupt-driven waits (socket data arrival) are serviced promptly — only timed wake-ups are gated. A Socket.Poll()-based wait does not trigger the issue, which gives a clean workaround.
Minimal repro (bisected step by step)
Test app: 3 threads (a Highest-priority watchdog sleeping 20 ms and counting overshoots >50 ms, a Normal-priority measuring thread, one 250 ms sleeper), no protocol logic. Each stage adds one subsystem; the numbers are Thread.Sleep(x) overshoot in ms, worst of 3:
| Stage | Configuration | 1 | 20 | 100 | 250 | 500 | stalls/sec | Verdict |
|---|---|---|---|---|---|---|---|---|
| 0 | bare app, internal heap | +0..21 | +0 | +0 | +0..19 | +0 | ~0 | clean |
| 1 | + extended heap (31 MB SDRAM) | +0..21 | +0 | +0 | +0 | +0 | ~0 | clean |
| 2 | + Ethernet EMAC, DHCP up | +0..21 | +0 | +0 | +0 | +0 | ~0 | clean |
| 3c | + one bound UDP socket, never used | +0..21 | +0 | +0 | +0 | +0 | ~0 | clean |
| 3a | + UDP socket serviced by Poll(2s) loop + 1 Hz SendTo broadcast |
+0..21 | +0 | +0..1 | +0 | +0..1 | ~0 | clean |
| 3b | + TCP listener parked in blocking Accept() (zero clients) |
+249 | +231 | +151 | +1 | +20 | ~16 | quantized |
| 3d | same listener, Poll(500ms) loop before Accept() |
+0..21 | +0 | +0 | +0..2 | +0 | ~0 | clean |
Stage 3b’s numbers show the wake-ups landing exactly on multiples of ~250 ms (+ up to one 20 ms quantum): Sleep(1) wakes at ~250, Sleep(100) at ~251, Sleep(250) is already gate-aligned (+1), Sleep(500) at ~520. The Highest-priority watchdog’s Sleep(20) overshoots >50 ms on essentially every iteration.
Repro core (stage 3b):
var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Any, 2323));
listener.Listen(2);
new Thread(() => { var c = listener.Accept(); /* never reached - no client */ }).Start();
// From this moment, on any other thread, any priority:
var t0 = DateTime.UtcNow;
Thread.Sleep(20);
// (DateTime.UtcNow - t0) is now ~250 ms
Real-world impact
We found this in a production device controller: the telnet server’s accept thread parks in Accept() from boot, so the entire firmware ran with ~4 whole-runtime stall events/sec (watchdog-measured, flat over an 18-hour telemetry capture), 250-380 ms wake latency on every periodic loop including a Highest-priority 250 ms control loop, and GetCpuUsageStatistic() pegged at ~100%. All of it disappears with the Poll workaround below.
Workaround (verified)
Never park a thread inside Accept()/Receive(); gate them with Poll so they complete immediately:
// accept loop
while (running)
{
if (!listener.Poll(500_000, SelectMode.SelectRead)) continue;
var client = listener.Accept(); // immediate
// receive loop
while (true)
{
if (!client.Poll(500_000, SelectMode.SelectRead)) continue;
var n = client.Receive(buf); // immediate; 0 on close
if (n == 0) break;
// ...
}
}
With this change our application’s stall counter went from ~4/sec to ~0 on the same firmware.
Environment
-
SCM20260D (SITCore SC20260), firmware v3.0.0.2000-prerelease (2026-06-11), libraries 3.0.0.3000-prerelease
-
Reproduces with and without the debugger attached, with internal or extended heap, idle or loaded, independent of thread count (tested 3 to 35 threads)
Questions
-
Is this known for the v3 prerelease? It looks like the managed timer/wake service shares a wait with the blocking-socket path, so sleeps are only serviced on the lwIP 250 ms slow timer while a blocking Accept/Receive is parked.
-
The firmware release is at v3.0.0.2000-prerelease but the libraries are at v3.0.0.3000-prerelease, are you planning to release a firmware aligned to the libraries.
-
Should I create this in your GitHub repo?
Link to the sample app: https://drive.google.com/file/d/1zum_c9F_3S1K1gE5E0gUuBd8qEiQTn17/view?usp=sharing