|
Motion Master 6.0.0-alpha.86
Next-generation motion control software
|
Routes whose handlers cannot block the event loop, because they do not run on it. More...
#include <uwebsockets/App.h>#include <BS_thread_pool.hpp>#include <atomic>#include <charconv>#include <chrono>#include <functional>#include <nlohmann/json.hpp>#include <optional>#include <string>#include <string_view>#include <utility>#include <vector>Go to the source code of this file.
Classes | |
| class | mm::api::Request |
| A request, snapshotted on the loop thread so a handler can outlive it. More... | |
| struct | mm::api::Response |
| A complete response, produced off the loop and written by the framework. More... | |
| class | mm::api::Router |
| Registers routes whose handlers run off the event loop. More... | |
Namespaces | |
| namespace | mm |
| namespace | mm::api |
| HTTP-transport glue shared by the built-in server and route plug-in libs. | |
Typedefs | |
| using | mm::api::Handler = std::function< Response(const Request &)> |
| What a route does: a pure function from a snapshotted request to a response. | |
Functions | |
| std::string | mm::api::percentDecode (std::string_view text) |
Percent-decodes a URL path component — %20 to a space, %2F to a slash. | |
| std::vector< std::string > | mm::api::parameterNames (std::string_view pattern) |
The :name tokens of a route pattern, in the order uWS will index them. | |
| Response | mm::api::json (const nlohmann::json &body) |
A 200 response carrying body as JSON. | |
| Response | mm::api::bytes (std::string contentType, std::string body) |
A 200 response carrying body verbatim under contentType. | |
| Response | mm::api::error (std::string status, std::string_view message) |
A status response carrying a {"error": message} body. | |
| Response | mm::api::badRequest (std::string_view message) |
A 400 with message — the most common failure, so it gets a name. | |
| Response | mm::api::notFound (std::string_view message) |
A 404 with message. | |
| Response | mm::api::statusOnly (std::string status) |
A bare status response with no body — a 202 that means "under way", a 204 delete. | |
| Response | mm::api::withWireTime (Response response, std::chrono::microseconds wireUs) |
Attaches the server-measured device time (X-Wire-Us) to response and returns it. | |
| template<typename Op > | |
| Response | mm::api::timed (Op &&op, std::string errorStatus="500 Internal Server Error") |
Runs a device operation, times it, and turns its std::expected into a timed response. | |
Routes whose handlers cannot block the event loop, because they do not run on it.
The problem this exists to remove. uWebSockets runs every handler on the app's single loop thread. A handler that blocks therefore blocks the whole HTTP API, not merely its own endpoint — and Motion Master is full of handlers that block for seconds by nature: an FoE transfer, an object-dictionary enumeration, any SDO waiting behind a busy control-plane lock. Measured during a firmware installation: a GET /api/devices/state waiting on the control-plane lock for a 12-second file transfer stalled every other request behind it, including /api/version, which touches no hardware at all, and the procedure-progress poll — the one thing the user was actually watching.
It could be fixed by wrapping each blocking handler by hand, and that was tried first. It is the wrong shape: it leaves the dangerous thing (touch the bus directly in a handler) as the default and the safe thing as something to remember, across seventy routes and every future one. So the framework does it instead — **a Handler cannot block the loop, because it never runs on it.**
What a handler is. A plain function from a Request to a Response. No uWS types, no response pointer, no lifetime rules, nothing to remember. It runs on a worker thread and returns a value; the framework writes it. That also makes handlers unit-testable without a server, which the old shape made impossible.
The three rules the framework keeps so a handler does not have to. A uWS response may only be touched from the loop thread — so every write happens inside uWS::Loop::defer. A response dies with its connection — so an abort flag, set by onAborted on the loop thread, is checked inside that same deferred callback, where the two cannot interleave. And a request object is valid only for the synchronous call — so it is snapshotted into a Request before anything is dispatched.