From b3897e71dc159f4182ffb5b24ff40b7e9411a96c Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 6 Aug 2026 13:19:30 +0100 Subject: [PATCH 1/2] Improve the documentation of `Blob` in particular around memory management. --- docs/source/blobs.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/source/blobs.rst b/docs/source/blobs.rst index d27c2b6d..e69afab8 100644 --- a/docs/source/blobs.rst +++ b/docs/source/blobs.rst @@ -14,7 +14,7 @@ A `.Blob` consists of some data and a MIME type, which sets how the data should Creating and using `.Blob` objects ------------------------------------------------ -Blobs can be created from binary data that is in memory (a `bytes` object) with `.Blob.from_bytes` or on disk (with `.Blob.from_temporary_directory` or `.Blob.from_file`). A `.Blob` may also point to remote data (see `.Blob.from_url`). Code that uses a `.Blob` should not need to know how the data is stored, as the interface is the same in each case. +Code that uses a `.Blob` should not need to know how the data is stored, as the interface is the same in each case. Blobs can be created from binary data that is in memory (a `bytes` object) with `.Blob.from_bytes` or on disk (with `.Blob.from_temporary_directory` or `.Blob.from_file`). A `.Blob` may also point to remote data (see `.Blob.from_url`). Blobs that are returned by a `~lt.ThingClient` will generally refer to the data using a URL, and download the data when it is accessed. Blobs offer three ways to access their data: @@ -162,13 +162,20 @@ It may be possible to have actions return binary data directly in the future, bu Serialising or deserialising `.Blob` objects generates URLs, which are specific to the HTTP request. This means that `.Blob` objects cannot be serialised or deserialised outside the context of an HTTP request handler, so if code in an Action or Property attempts to turn a `.Blob` into JSON, it is likely to raise exceptions. For more detail on this mechanism, see `.middleware.url_for`\ . +Class structure +--------------- + +The public interface is provided by `.Blob`, which is the only class most code should need to interact with. Internally, `.BlobData` is responsible for implementing the different back-end methods that manage data in memory or on disk. In principle it would be possible to implement a custom `.BlobData` subclass, though this is not currently considered part of the public LabThings API. `.BlobData` is subclassed into `.LocalBlobData` and `.RemoteBlobData`, and local is further subclassed to use either a file or a `bytes` object to store the data. + Memory management and retention ------------------------------- -Management of `.Blob` objects is currently very basic: when a `.Blob` object is returned in the output of an Action that has been called via the HTTP interface, it will be retained as long as the action's output. This may be set on each action, and defaults to 5 minutes. This should be improved in the future to avoid memory management issues. +A `.Blob` instance holds a strong reference to exactly one `.BlobData` object, which refers to data on memory, on disk, or at a remote network location. Using class methods, it's possible to retrieve a `.BlobData` object using a unique ID. This is used internally by LabThings to allow the data to be downloaded, and to allow Blobs to be passed as input to actions. Only a weak reference is retained by the class, so this mechanism does not prevent the data from being finalised: a strong reference to the `.Blob` object must exist somewhere, or the data will be discarded. + +When a `.Blob` object is returned in the output of an Action that has been called via the HTTP interface, it will be retained as long as the action's output by the `.ActionManager`. The retention period may be set on each action, and defaults to 5 minutes. This should be improved in the future to avoid memory management issues. -When a `.Blob` is serialised, a URL is generated with a unique ID to allow it to be downloaded. However, only a weak reference is held to the `.Blob`. Once an Action has finished running, the only strong reference to the `.Blob` should be held by the output property of the action invocation. The `.Blob` should be garbage collected once the output is no longer required, i.e. when the invocation is discarded - currently 5 minutes after the action completes, once the maximum number of invocations has been reached or when it is explicitly deleted by the client. +When a `.Blob` is serialised, a URL is generated with its unique ID to allow it to be downloaded. As described above, only a weak reference is held to the `.Blob`. Once an Action has finished running, the only strong reference to the `.Blob` should be held by the output property of the `.Invocation` retained by the `.ActionManager`. The `.Blob` will be finalised by Python once the output is no longer required, i.e. when the invocation is discarded - currently defaulting to 5 minutes after the action completes, once the maximum number of invocations has been reached, or when it is explicitly deleted by the client. The behaviour is different when actions are called from other actions. If `action_a` calls `action_b`, and `action_b` returns a `.Blob`, that `.Blob` will be subject to Python's usual garbage collection rules when `action_a` ends - i.e. it will not be retained unless it is included in the output of `action_a`. From 2837fa40fdcf555de8dc3003904f01ed9b1fabf5 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Thu, 6 Aug 2026 14:05:25 +0100 Subject: [PATCH 2/2] Add a diagram showing HTTP flow and memory management --- dev-requirements.txt | 5 +++++ docs/source/blobs.rst | 2 ++ docs/source/conf.py | 1 + docs/source/diagrams/blob_http_flow.mermaid | 19 +++++++++++++++++++ pyproject.toml | 1 + 5 files changed, 28 insertions(+) create mode 100644 docs/source/diagrams/blob_http_flow.mermaid diff --git a/dev-requirements.txt b/dev-requirements.txt index 02fd2369..20540c39 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -154,6 +154,7 @@ jinja2==3.1.6 # sphinx # sphinx-autoapi # sphinx-jinja2-compat + # sphinxcontrib-mermaid jsonschema==4.26.0 # via # labthings-fastapi @@ -250,6 +251,7 @@ pyyaml==6.0.3 # fastapi # myst-parser # sphinx-autoapi + # sphinxcontrib-mermaid # uvicorn referencing==0.37.0 # via @@ -305,6 +307,7 @@ sphinx==8.1.3 # sphinx-tabs # sphinx-toolbox # sphinxcontrib-jquery + # sphinxcontrib-mermaid sphinx-autoapi==3.8.0 # via labthings-fastapi sphinx-autodoc-typehints==3.0.1 @@ -329,6 +332,8 @@ sphinxcontrib-jquery==4.1 # via sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx +sphinxcontrib-mermaid==2.1.0 + # via labthings-fastapi sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 diff --git a/docs/source/blobs.rst b/docs/source/blobs.rst index e69afab8..1f060a3c 100644 --- a/docs/source/blobs.rst +++ b/docs/source/blobs.rst @@ -156,6 +156,8 @@ In order to run an action and download the data, currently an HTTP client must: * Poll the invocation until it is complete, and the `.Blob` is available in its ``output`` property with the URL and content type. * Download the data from the URL in the `.Blob` object, which will return the binary data. +.. mermaid:: diagrams/blob_http_flow.mermaid + It may be possible to have actions return binary data directly in the future, but this is not yet implemented. .. note:: diff --git a/docs/source/conf.py b/docs/source/conf.py index 8cd8b64c..430d1c3d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,6 +29,7 @@ "sphinx_rtd_theme", "sphinx_toolbox.decorators", "myst_parser", + "sphinxcontrib.mermaid", ] myst_heading_anchors = 3 diff --git a/docs/source/diagrams/blob_http_flow.mermaid b/docs/source/diagrams/blob_http_flow.mermaid new file mode 100644 index 00000000..f3c791a0 --- /dev/null +++ b/docs/source/diagrams/blob_http_flow.mermaid @@ -0,0 +1,19 @@ +sequenceDiagram + Client->>+Server: POST /camera/capture_image + Server->>+Camera: Run "capture_image" + Server-->>-Client: 201 Invoked successfully + note over Camera: Take image + create participant Blob + Camera->>Blob: Create from bytes + Client->>+Server: GET /action_invocations/{id} + Server-->>-Client: 200 Running + Camera->>-Server: Complete + note over Server: The Blob is retained as part
of the Invocation's output. + Client->>+Server: GET /action_invocations/{id} + Server-->>-Client: 200 Complete
(includes blob URL) + Client->>+Server: GET /blobs/{blob_id} + Blob->>Server: Response object containing binary data + Server->>-Client: 200 Binary data + note over Server: ~5 minutes later, the invocation
expires and is deleted + destroy Blob + Server-xBlob: \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b021ff4d..7b6e9cf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ dev = [ "sphinx>=7.2", "sphinx-autoapi", "sphinx-toolbox", + "sphinxcontrib-mermaid", "sphobjinv", "myst-parser<5", "tomli; python_version < '3.11'",