Migration Guide (4.x to 5.0.0)
ioiocore 5.0.0 is a major release. This guide lists the breaking changes and what downstream code (for example, node libraries built on ioiocore) must do to upgrade. Items are ordered roughly by how likely they are to affect you.
Class source is no longer transported in serialization
Serialized payloads no longer carry class source code, and deserialization no
longer executes any source field. Node classes are resolved by importing
their module and looking them up by name.
What to do
Make sure every custom node class is importable on the host that deserializes the pipeline (install or ship its package). A class that is not importable now raises a clear
TypeErrorinstead of silently running embedded source.Portable.add_preinstalled_module()still exists but is a no-op; you can remove calls to it.If you relied on shipping node code inside the payload, ship the package instead (e.g. bundle and unpack it onto
sys.pathbefore deserializing).
Constructors must not pass a config field both explicitly and via kwargs
Deserialization rebuilds a node with cls(**config), where config
already contains input_ports / output_ports / name / … A
constructor written like this collides on the round trip:
# BROKEN on deserialize: 'input_ports' passed twice
def __init__(self, **kwargs):
super().__init__(input_ports=[IPort.Configuration()], **kwargs)
What to do — default into kwargs instead of passing alongside it:
def __init__(self, **kwargs):
kwargs.setdefault("input_ports", [IPort.Configuration()])
super().__init__(**kwargs)
If you get it wrong, deserialization now raises an actionable TypeError
naming the class and the fix, instead of Python’s opaque duplicate-keyword
error.
The data path copies on push
OPort.push() now gives each connected input port its own deep copy, and
consumers no longer copy on receive.
What to do — usually nothing. Producers may now safely reuse a preallocated output buffer between cycles, and fan-out consumers each receive an independent snapshot. Do not assume two consumers share the same object.
Pipeline.start() auto-resets after an error
Previously, once any node errored the pipeline was permanently unstartable.
Now start() clears the error and resets per-node run state (counter,
context, queued data), so the pipeline can run again. The recovery is logged.
What to do — put retry policy (backoff, maximum attempts) in your
supervisor. ioiocore does not throttle a start() / error / start()
loop, so a persistent fault could otherwise be retried indefinitely.
The pipeline stops on excessive input backlog
A consumer that cannot keep up used to grow memory without bound. The monitor
now logs a warning past ConstantsImp.BACKLOG_WARNING queued items and
stops the pipeline (logging an error) past ConstantsImp.BACKLOG_ERROR.
What to do — tune those thresholds to your block rate if the defaults
(1000 / 10000 items) do not fit, and inspect Pipeline.get_backlog() if you
monitor pipeline health yourself.
source_delay has been removed
ONode.source_delay and the pipeline’s source-delay equalization are gone;
the value was never applied. Remove any use of source_delay.
Chains derive their ports from their boundary nodes
A chain exposes its boundary node’s ports. Passing input_ports /
output_ports to a chain whose names do not match the boundary node now
raises at construction instead of silently discarding them. Configure the
boundary node’s ports instead of the chain’s.
Port accessors: get_port() is deprecated
Use the direction-explicit accessors, available on every input/output-capable node and chain:
node.get_input_port("in")
node.get_output_port("out")
chain.get_input_port("in")
chain.get_output_port("out")
Chain.get_port() still works but emits a DeprecationWarning and will be
removed in a future release.
Chain get_counter() / get_context() now delegate
They now report the chain’s last internal node instead of always returning
0 / None, so a chain behaves like a node for liveness and context.
Release logging resources with close()
The logger’s writer thread and file handle are released when the pipeline is
disposed, not on every stop(). Call Pipeline.close() when you are done
with a pipeline, or use it as a context manager:
with ioc.Pipeline() as pipeline:
pipeline.start()
...
pipeline.stop()
# logger flushed, file closed, thread joined on exit
If the log directory cannot be created the pipeline now runs with in-memory
logging only and reports it via Pipeline.is_logging_persistent() and a
startup message.
Optional configuration keys
Any key declared in a Configuration.Keys class is required. For values
that may be absent, declare them in a Configuration.OptionalKeys class
instead; they are validated when present but never required. The
required-field error now names the declaring class.
Linux support
The package now imports on Linux (the default log directory is resolved lazily using the XDG path), in addition to Windows and macOS.