Known issues & current limits
What doesn’t work (yet), as of v0.38.0. Items here are confirmed and tracked — not speculative.
Networking (Pico 2 W)
- Personal auth only. Open, WPA2-AES, and WPA3-SAE (
PICODROID_WIFI_AUTH) — no WPA2/WPA3-Enterprise. IPv4 only. - TLS is RP2350-only and minimal.
https://works on boards withhas_tls = true(every RP2350 WiFi board; the RP2040 has no flash for it): TLS 1.3,TLS_AES_128_GCM_SHA256, P-256 key exchange, a compiled-in store of 14 roots, no client certificates, no session resumption. A handshake is 1.1–2.0 s and needs the wall clock set, which the platform’s time service does from the network once the link is up (a handshake that beats it waits up to 8 s). See HTTPS. - Socket throughput is chunked. Socket I/O crosses the native boundary in 256-byte chunks; large transfers work correctly but pay a per-chunk cost.
- The device closes HTTP connections with RST. After a complete response the FreeRTOS+TCP side resets rather than closing cleanly, so a client sees a transport error alongside a full, correct payload (
curlexits 56 withhttp_code200). Scripts probing a device HTTP server must judge success by the status code and body, not the client’s exit code. Simulator connections close normally. - Boot-time race. The network takes up to ~10 s after reset (WiFi join + DHCP); an app opens its first socket from a
ConnectivityManager.NetworkCallback’sonAvailable, or, with no Activity to deliver callbacks, pollsNetworkInfo.isConnected()first — see WiFi & networking setup. - No
LinkProperties.ConnectivityManagerhas Android’sNetworkCallback,Network,NetworkCapabilitiesandNetworkRequest, but noLinkProperties/onLinkPropertiesChanged: the address isNetworkInfo.getIpAddress(), and a DHCP renewal that changes it fires no callback.onLosing/onUnavailableare never called.
Concurrency
- Equal-priority threads do not round-robin. Only one task interprets Java at a time: every Java task runs at one FreeRTOS priority with time slicing off, and since 2026-09-15 holds a kernel mutex (the JVM run lock) that it gives up only where it blocks — that is what keeps the shared heap safe without per-object locks — so a thread yields only when it blocks. A compute-bound thread therefore starves its siblings until it calls
sleep,join,wait, or blocking I/O. A safepoint-yield fix is designed but ungated on performance; seedocs/quality-roadmap.md. Thread.setPriorityis advisory. The value is stored and reported back, never applied, for the same reason.volatileis ignored and the interpreter emits no memory barriers. Correct only while a single core interprets Java, which is the invariant every JVM-adjacent task is pinned to. As of 2026-08-31 it is enforced rather than remembered:platforms/rp/src/task_affinity.rsis the only way the RP family creates a task — it names the core and makes create+pin scheduler-atomic — and its source scan failsscripts/test.shfor any spawn that bypasses it. What remains isvolatileitself, which parses and is ignored — tracked indocs/quality-roadmap.md(THR-04 / X1 indocs/parity-audit.mdrecords the trace).java.util.concurrentis excluded ontestbench_rp2040. The board drops thepicodroid.concurrentpool,Future, atomic and latch classes to stay inside its flash budget, so an app using them fails to resolve there rather than failing at runtime.Thread,synchronizedandObject.wait/notifyare available on every board.picodroid.jsonis a board capability. Only boards withhas_json = truein theirboard.tomlshipJSONObject/JSONArray/JSONException(every RP2350 board today; nottestbench_rp2040). The native node pool behind them is capped at 2048 nodes and 16 KiB of string bytes across all live documents: a larger parse throwsJSONException, a largerputthrowsOutOfMemoryError.picodroid.protobufis a board capability. Only boards withhas_protobuf = truein theirboard.tomlshipCodedInputStream/CodedOutputStreamand the classes beside them (every RP2350 board today; nottestbench_rp2040). An app built for a board without it failsverifyApiContractrather than resolving on the device.- Spurious wakeups are possible.
Object.waitmay return without a matchingnotify, as the JLS allows — always wait in a loop over the condition.
Simulator ↔ hardware gaps
- The sim is single-core. The simulator runs the real FreeRTOS kernel, but on the single-core POSIX port — cross-core interactions (and cross-core races) only exist on hardware.
- Finished threads park instead of exiting. A sim thread whose
run()returns leaves its FreeRTOS task parked; an app churning through tens of thousands of short-lived threads will exhaust host threads. Long-running worker threads are unaffected. - Sim networking is the host stack.
picodroid.netin the simulator uses your machine’s network directly — connection timing, buffer limits, and error codes differ from the device’s FreeRTOS+TCP stack. - macOS is untested since the simulator moved onto the FreeRTOS scheduler. Linux (windowed and headless) is exercised continuously.
Platform
- RP2040 flash is tight. A
--releasetestbench_rp2040image sits at 81% of the 1152 K program region (964,704 of 1,179,392 bytes forhelloworldat the 2026-09-28 size baseline), and a committed size ratchet gates any growth. The region was 896 K until the framework’s class link tables moved into flash and overflowed it by about 40 KB; the board’s app region gave up 256 KB (1024 → 768 KB) to make the room.scripts/build.shdisables LTO on RP2040 (which paradoxically shrinks the image) and leaves line numbers out of every firmware image (since 2026-10-14; the RP2040 since 2026-09-26); a rawcargo build --releasefor RP2040 links a larger image than the one the scripts measure. - BME688 gas resistance is constant on hardware. The gas sensor’s heater profile is never programmed, so gas/IAQ readings sit at a fixed value on the device (temperature, humidity, and pressure are fine). Affects the picoenvmon IAQ tile cosmetically.
Where these are tracked
Networking items carry NET-* IDs in docs/networking-followups-2026-08.md; broader quality items live in docs/quality-roadmap.md.