Troubleshooting¶
This page covers the most common problems encountered when starting with Sen, in a symptom → cause → fix format. For a deeper understanding of why things work the way they do, see Understanding Sen: a mental model and Working with objects.
Quick diagnostics checklist¶
Run through this before anything else:
- Is the Sen environment sourced? (
sen --versionshould print a version or a commit hash) - Is
LD_LIBRARY_PATHset to include your build output? (e.g.$(pwd)/build/lib) - Did CMake run successfully and did
make(or similar) complete without errors? - Does your YAML config reference the correct class names (case-sensitive,
package.ClassName)? - Do all bus names in YAML exactly match what your code uses?
- Does every exported class have
SEN_EXPORT_CLASS(ClassName)at the bottom of its.cpp?
Build and environment¶
Package fails to load at runtime: dlopen error or cannot open shared object file¶
Symptom: Sen starts but immediately prints something like:
Runtime error: libmy_package.so: libmy_package.so: cannot open shared object file: No such file or directory
Cause: The shared library was built but the runtime linker cannot find it. LD_LIBRARY_PATH
does not include your build output directory.
Fix:
Put your own directory first: appending it instead means a library of the same name installed system-wide would be found before the one you just built.
Add this to your shell profile or a project-local setup.sh script so you do not have to run it
every time.
Tip
Run ldd build/lib/libmy_package.so to verify all transitive dependencies are also found.
Generated .stl.h files are missing or stale¶
Symptom: Build fails with #include "stl/my_package/my_class.stl.h": No such file or directory,
or you added a property in your STL file but it does not appear in the C++ class.
Cause: The code generator has not been run since the STL file was created or modified.
Fix: The generator runs automatically as part of the CMake build if you used add_sen_package()
correctly. Run a full clean build:
If the file is still missing, check that your STL files are listed in the STL_FILES argument of
add_sen_package() in your CMakeLists.txt:
add_sen_package(
TARGET my_package
...
STL_FILES
stl/my_package/my_class.stl # ← must be listed here
)
Object discovery¶
Object never appears in the term / subscription is always empty¶
Symptom: You run ls in the term and see nothing, or your Subscription<T> list
is always empty even though the component started successfully.
Cause (most common): Bus name mismatch. The object is published to a different bus than the one you are watching.
Fix: Check that the bus name in your YAML config exactly matches what you use in code and in the term. Bus names are case-sensitive:
# YAML config
objects:
- class: my_package.MyClassImpl
name: obj1
bus: local.my_bus # ← this must exactly match
// In registered():
workers_ = api.selectAllFrom<WorkerInterface>("local.my_bus"); // ← must be identical
Object appears, then immediately disappears¶
Symptom: The object briefly shows up but vanishes within a cycle or two.
Cause 1: The object is being destroyed before the component finishes registering, typically because it is a local variable that goes out of scope.
Fix: Objects must be owned by the component (stored as shared_ptr members), not as locals.
Cause 2: Subscription<T> is a local variable. When it goes out of scope it tears down the
subscription: the list is cleared, and any onRemoved callback you installed fires for every
object the list was holding. The object itself is untouched. It is still on the bus and ls in
the term still lists it, so what disappeared is your view of it.
Fix: Make the subscription a member variable of your class. selectAllFrom returns a
std::shared_ptr<sen::Subscription<T>>, so that is the type the member has to hold:
class MyManagerImpl : public MyManagerBase
{
// ✅ correct — member, lives as long as the object
std::shared_ptr<sen::Subscription<WorkerInterface>> workers_;
void registered(sen::kernel::RegistrationApi& api) override
{
workers_ = api.selectAllFrom<WorkerInterface>(getWorkerBus());
// ❌ wrong — local, torn down on leaving registered()
auto workers = api.selectAllFrom<WorkerInterface>(getWorkerBus());
}
};
For a complete version that compiles and runs, see the calculators example. Its client subscribes
this way, and Working with objects shows the same code pulled straight
from it.
Two objects with the same name¶
Symptom: You instantiate two objects but only one appears, or behavior is inconsistent.
Cause: Object names must be unique within a bus. If two objects share a name, one will be rejected.
Fix: Ensure each object has a distinct name in the YAML config:
objects:
- class: my_package.MyClassImpl
name: object1 # unique
bus: local.workers
- class: my_package.MyClassImpl
name: object2 # unique
bus: local.workers
Properties and state¶
Property changes are not visible to other objects¶
Symptom: You update a property in your update() function but other components always see the
old value.
Cause 1: You are assigning to a local variable or calling a non-staging setter, instead of
using setNext<Prop>().
Fix: Always use the generated setNext<Prop>() accessor to stage
property changes:
// ✅ correct — stages the change for commit
setNextPosition(newPosition);
// ❌ wrong — this does nothing to the Sen property
position_ = newPosition;
Cause 2: You are calling setNext<Prop>() from a non-Sen thread.
Fix: Stage property changes from update(). Initialize values using the YAML config
(prop_name: initial_value) or in registered() via the appropriate API.
Startup crash: "missing constructor argument"¶
Symptom: The kernel stops at startup with
missing constructor argument'step' of type 'i32' for creating object 'obj' of class
'my_counter.CounterImpl', naming a [static] property.
Cause: Static properties require an initial value at construction time, and none was provided in the YAML config.
Fix: Add the missing initial value to the object's YAML entry:
objects:
- class: my_package.MyClassImpl
name: obj1
myStaticProp: "required value" # ← add this
bus: local.example
The YAML key is the property name as defined in STL.
Method callback fires much later than expected¶
Symptom: The result arrives several cycles after the call, not on the very next one.
Cause: This is normal behavior under load. If the target component is running at a low frequency (e.g. 2 Hz) and the call is made just after its drain, the next drain is almost a full period away.
Fix: Increase the component frequency if lower latency is required, or accept the asynchronous nature of the system. This is not a bug.
Exception from a method is silently ignored¶
Symptom: Your method implementation throws, but the caller never sees the error.
Cause: The MethodResult<R> returned to the callback contains the exception, but the callback
does not check for it.
Fix: Always check isOk() before calling getValue():
calc->divide(10.0F, 0.0F, {this, [](const sen::MethodResult<float32_t>& r) {
if (r.isOk())
{
std::cout << "result: " << r.getValue() << std::endl;
}
else
{
try
{
std::rethrow_exception(r.getError());
}
catch (const std::exception& e)
{
std::cout << "error: " << e.what() << std::endl;
}
}
}});
Working with objects shows the same pattern taken from the
calculators example, which compiles as part of the build.
Configuration¶
Component starts before its dependency is ready¶
Symptom: A component tries to find objects from another component but finds nothing on the first few cycles. Or the kernel logs errors about missing objects during initialization.
Cause: Both components are in the same group, so their startup order is not guaranteed. Or the dependent component is in a lower group number than its dependency.
Fix: Put dependencies in lower group numbers. Higher groups start after all lower groups are fully running:
build:
- name: my_data_source
group: 2 # starts first
- name: my_consumer_component
group: 3 # starts after group 2 is fully up
See Using component groups for the full rules.
Class not found by kernel: "could not find type" error¶
Symptom: The kernel stops at startup with
could not find type 'my_package.MyClassImpl' in any of the imported libraries (symbol
senGetType0x385c6ccb not found). The hexadecimal symbol is derived from the type name, so it
changes with the class you asked for.
Cause 1: SEN_EXPORT_CLASS(MyClassImpl) is missing from the .cpp file.
Fix: Add the macro at the bottom of the implementation file:
Cause 2: The package is not listed in the imports of the component that tries to instantiate
the class.
Fix: Add it to the imports list in your YAML config (see the discovery section above).
Cause 3: The class name in YAML uses the wrong case or wrong separator. The format is always
package_name.ClassName: the package name from your STL package declaration, dot, then the
C++ class name.
YAML config error: wrong type¶
Symptom: The kernel refuses to start. The message can be very terse. A string where an i32 is
expected reports only Implementation error: stol, naming neither the key, the object nor the
expected type. stol is the standard-library conversion that failed.
Cause: The value type does not match the STL declaration, for example a string where a number is expected.
Fix: Check that numeric properties use numbers in YAML rather than quoted strings, and that
every required [static] property is present.
A misspelled property name is not reported at all¶
Symptom: The object starts, but a property you set in YAML has its default value instead of yours. Nothing is logged.
Cause: Nothing checks an object's YAML keys against the properties its class declares. The
constructor reads the keys it knows about and ignores the rest, so a misspelled name is not a key
anybody is looking for. The kernel cannot report it, because name, class and bus live in the
same map and are not properties either.
Fix: Check the key against the STL property name, which is case-sensitive. Generating a JSON schema for your package and pointing your editor at it turns this into an editor warning, which is the only place it gets caught; see The configuration file.
Networking¶
Objects not discovered across processes¶
Symptom: You run two sen processes on the same machine (or different machines) but their
objects do not see each other.
Cause: The ether component (Ethernet transport) is not loaded in one or both processes.
Fix: Add the ether component to both process configs and configure matching addresses. See
the Ether component documentation for the full configuration reference.
A firewall or a restricted environment blocks Sen¶
Symptom: Processes work on a development machine and stop discovering each other once deployed, or a security team asks which ports and addresses Sen needs before they will allow it.
Cause: Sen uses a discovery port, a multicast address per bus, and a TCP and UDP port per process. In a restricted environment none of these are open by default, and some are chosen at runtime rather than written in the configuration.
Fix: Get the list from Sen rather than reading it off the configuration by hand. Before deploying, the offline network footprint reports what a process expects to use. That is the list a security team needs in order to open it. Once deployed, the runtime network footprint reports what the process has bound, including the ephemeral ports the operating system chose, so you can check that what was opened matches what is used.
WSL2 networking issues¶
Symptom: Sen processes in WSL2 or a virtual machine do not discover processes on other hosts, even though ordinary TCP connections work.
Cause: The virtualized network interface, host firewall, or virtual switch is not forwarding multicast traffic. WSL2 also needs mirrored networking and a sufficiently high open-file limit.
Fix: Follow Networking with WSL2 and virtual machines. It covers the verified WSL2 setup, network-interface selection, socket limits, firewalls, known connectivity limitations, and the TCP-only fallback.
Crashes¶
A process disappeared, or died of a signal¶
Symptom: A Sen process ended without a clean shutdown. A supervisor reports it as killed by a signal rather than as exiting with a status, or it vanished and left nothing on the terminal.
Cause: How Sen answers this depends on how the crash arrived. A fault — a bad access, a division by zero — never reaches Sen's own code at all, so the report is written from outside by a separate handler process, as a minidump. An uncaught exception does reach Sen, which writes a report itself and then aborts, so that route leaves both a report and a dump.
Fix: Look under the directory crashReportDir names. Nothing there means either the handler
never armed, which the kernel log says on the line beginning "the crash handler could not be
started", or ptrace was refused: check cat /proc/sys/kernel/yama/ptrace_scope, where 2 or 3
means no minidump. Crash reports covers what each file holds, how
to read the Sen-specific information out of a dump, and how to switch the whole thing off.
A crash's frames have no function names¶
Symptom: A dump or a core opens, but the frames show addresses and a module rather than function names, files and lines.
Cause: A release build is stripped, so the binaries carry no description of which address
belongs to which function. That description ships separately, in the -symbols archive,
and it has to be the one built for the binaries that crashed.
Fix: Getting Sen sets out which of the
four archives answers which question, and
Reading a crash has the debugger invocation. Note
that -relwithdebinfo is a different build of the same source rather than the release build with
symbols added, so pointing it at a release dump resolves nothing useful.