.. _architecture: Architecture and Threading ========================== This page describes the internal structure of CosmicSDK, the threading model it relies on, and the thread-safety guarantees (and limitations) that apply to every API surface the SDK exposes. SDK Layers ---------- CosmicSDK is structured as a five-layer stack. From lowest to highest: .. list-table:: :header-rows: 1 :widths: 28 72 * - Layer - Responsibility * - ``CosmicDriver`` - Opens the USB HID device via HIDAPI, enumerates by VID/PID, and owns a dedicated polling thread that continuously reads raw bytes from the hardware. * - ``UsbTransferProtocol`` - Frames raw bytes into USB transfer packets. * - ``CommunicationManager`` - Owns the ``CosmicDriver`` and ``UsbTransferProtocol`` instances. Frames outgoing requests into USB packets and forwards them to the driver; reassembles incoming multi-packet IN transfers into single complete messages; routes each completed message to a registered callback. * - ``CosmicAsyncSDK`` - The public asynchronous API. Commands are fire-and-forget; results arrive through a user-registered ``onResponse()`` callback invoked by the polling thread. * - ``CosmicSDK C++ API`` - The public blocking C++ API. Wraps ``CosmicAsyncSDK`` via an internal ``BlockingApi`` that suspends the calling thread until the firmware. * - ``CosmicSDK C API`` - The public blocking C API. Exposes the C++ API as plain C functions through a handle pointer. Language bindings (Python, Java, C#) sit above ``CosmicSDK`` and call it through the C shared library (``CosmicSDK_API_c.h``). Internal Threading Model ------------------------ One OS thread per ``CosmicDriver`` instance — the *polling thread* — reads from the USB HID device in a tight loop for the lifetime of the connection. It delivers each complete response to the ``CommunicationManager``, which dispatches it to the registered callback. For blocking calls (``CosmicSDK``), the ``BlockingApi`` layer parks the calling thread on a per-request condition variable and wakes it once the polling thread delivers the matching response. .. warning:: Notification callbacks (I3C hot-join/IBI, GPIO, UART) fire **on the polling thread**. Keep their bodies short and non-blocking. Issuing any SDK call from inside a notification callback will deadlock because the polling thread cannot deliver its own response. Thread Safety ------------- .. warning:: **The C/C++ API and all language bindings are not thread-safe.** A ``CosmicSDK_Handle`` — and the equivalent wrapper objects in Python, Java, and C# — must not be called concurrently from multiple threads without external synchronization. The SDK does not protect against concurrent access: two threads issuing commands on the same handle simultaneously will corrupt internal state. .. list-table:: :header-rows: 1 :widths: 35 15 50 * - API surface - Thread-safe? - Notes * - ``CosmicSDK`` (C++ blocking) - No - Caller must serialize all calls on a given instance. * - ``CosmicAsyncSDK`` (C++ async) - No - Caller must serialize all calls on a given instance. * - C API (``CosmicSDK_Handle*``) - No - Wraps the C++ API; inherits the same constraint. * - Python binding (``CosmicPySDK``) - No - Python GIL does not protect the underlying C calls. * - Java binding (``CosmicSdk``) - No - JNA provides no automatic synchronization. * - C# binding (``CosmicSdk``) - No - P/Invoke calls are unguarded. Recommended Pattern ------------------- Protect every call on a shared handle with a single mutex. The mutex must be held for the entire duration of the call — the SDK is blocking, so the calling thread holds it for the full firmware round-trip. **C++ (std::mutex)** .. code-block:: cpp #include std::mutex sdk_mutex; CosmicSDK sdk; // In any thread: { std::lock_guard lock(sdk_mutex); sdk.I2cControllerInit(I2C_BUS_0, 100000, I2C_PULLUP_INTERNAL); } **C API** .. code-block:: c /* Acquire your mutex before every call that uses the same handle. */ your_mutex_lock(&sdk_mutex); CosmicSDK_I2cControllerInit(handle, I2C_BUS_0, 100000, I2C_PULLUP_INTERNAL); your_mutex_unlock(&sdk_mutex); **Python** .. code-block:: python import threading lock = threading.Lock() # In any thread: with lock: sdk.i2c_controller_init(I2C_BUS_0, 100000, I2C_PULLUP_INTERNAL) **Java** .. code-block:: java // In any thread: synchronized (sdk) { sdk.i2cControllerInit(I2C_BUS_0, 100000, I2C_PULLUP_INTERNAL); } **C#** .. code-block:: csharp // In any thread: lock (sdk) { sdk.I2cControllerInit(I2cBus.Bus0, 100000, I2cPullup.Internal); } .. tip:: If all SDK calls originate from a single thread, no mutex is needed. Locking is relevant only when multiple threads share the same handle.