Skip to content

[DRAFT] Priority-based scheduler for federated applications#328

Draft
fra-p wants to merge 18 commits into
mainfrom
priority
Draft

[DRAFT] Priority-based scheduler for federated applications#328
fra-p wants to merge 18 commits into
mainfrom
priority

Conversation

@fra-p
Copy link
Copy Markdown
Collaborator

@fra-p fra-p commented Jan 2, 2026

This draft pull request is the work @edwardalee and I have been doing to introduce priority scheduling of reactions with deadline for federated applications with the reactor-uc runtime. It is still a draft, because testing is incomplete and the syntax for some customization options is to be discussed (more on this in a dedicated section). However, I am creating this pull request to get feedback on the priority assignment rule and the scheduler design that would certainly help improve the scheduler. Feel free to comment with thoughts and suggestions!

Introduction

The reactor-uc runtime features a single OS thread that schedules the reactions in the reaction queue. In federated applications, each federate has its own runtime and its own thread. When the federates run on the same machine, the threads compete for the CPUs of the platform, and a scheduling algorithm handles contention.

Federates may have different urgency of execution depending on what reaction they are executing at a given time. In fact, reactions can be assigned with a deadline. To guarantee that deadlines are met, a real-time thread scheduler is needed to prioritize the completion of reactions with more urgent deadlines.

This PR implements a priority-based scheduler in reactor-uc that assigns a priority value to a reaction as a function of its deadline. The rule is based on the deadline monotonic (DM) principle, but extended to support platforms like Linux that feature a finite number of assignable priority values.

Scheduler design

Control flow

The main thread of each federate serves one reaction at a time. The thread picks the first ready reaction in the reaction queue and sets its priority to a value that depends on the deadline of the picked reaction. The OS thread scheduler will use that priority value to schedule the thread together with the threads of the other federates on the same machine.

Once a federate is done with the reactions in the reaction queue and waits until its physical time catches up with the next tag, it sets its priority to a value that is higher than any possible main thread executing reactions. This is needed to handle the case of asynchronous events (e.g., physical connections) and guarantees that the thread is woken up as soon as the event arrives.

Federates rely on TCP connections to communicate, and the connections are handled by the TCP thread. Similarly to the previous case, the priority of the TCP thread is higher than any sleeping thread (and thus, any thread executing reactions), to be sure that the messages are received without delays. However, this might be a problem with large messages.

Priority function

The priority value for a thread executing a reaction with deadline $d$ is determined by the priority function $prio(d)$, which maps the deadline range [$d_{min}, d_{max}$] to the priority range [${prio}{min}, {prio}{max}$]. $d_{min}$ and $d_{max}$ are the minimum and maximum deadlines found in a given federated application, while ${prio}{min}$ and ${prio}{max}$ are the minimum and maximum scheduling priorities that can be assigned to threads scheduled with a given OS thread scheduler. The function is decreasing exponential to subsume the deadline monotonic rule: tighter deadlines are mapped to higher priority values. This function is tailored to the specific federated application and generated by the code generator.

The analytical expression of the priority function is:

$prio(d) = round(K e^{- \alpha * d} + P)$.

$\alpha$ is the parameter that controls the speed of decay of the exponential function. $K$ and $P$ are computed such that:

  • $prio(d_{min}) = {prio}_{max}$;
  • $prio(d_{max}) = {prio}_{min}$.

Since the priority space is finite, collisions may occur: two very close deadline values may be mapped to the same priority value. The priority function allows to control in what part of the deadline range collisions are more likely to occur by tuning the $\alpha$ parameter. A big $\alpha$ value increases the speed of decay of the function, which means that most of the priority space is used to map small deadlines. This is useful when most of the deadlines of the federated application are tight and we do not want collisions to occur with these. Collisions occurring with looser deadlines are likely to cause less harm. A small $\alpha$ value decreases the speed of decay, and the priority function performs a priority mapping that tends to a linear conversion. $\alpha$ is automatically selected in the range [$\alpha_{min}$, $\alpha_{max}$] (empirically derived) by the code generator as a function of the median deadline of the federated application, to make the priority function better fit its deadline distribution.

Implementation details

The priority scheduler is a sublcass of the dynamic scheduler and is called PriorityScheduler. It only works with platforms having threading capabilities that are modeled by the ThreadedPlatform class.

The only supported platform at the moment is Linux. Both SCHED_FIFO and SCHED_RR can be selected as underlying thread scheduling algorithms. The POSIX thread API is used to control the thread scheduler.

Federate's main threads can be assigned with priority values in the range [2, 97]. 1 is reserved to reactions with no deadline; 98 is the sleeping thread's priority; 99 is the TCP thread's priority.

The PriorityScheduler_run_timestep function sets the priority of the current thread right before executing the reaction body by calling the priority function.

Example

Tasks.lf.txt
Attached is a simple example of a federated real-time application in which deadline violations occur when it is not executed with the priority scheduler. It is made of two periodic tasks both triggered by a 50 ms timer, one of which has a tighter deadline and a longer execution time than the other. The more urgent task starts at a slightly greater logical time, which means that when its timer fires, the loose-deadline task is already executing. This test case shows the ability of the priority scheduler to preempt the loose-deadline task to execute the more urgent one. With the default scheduler, preemption does not happen and the urgent task misses its deadline.

Future work

Currently, the priority scheduler is enabled when either SCHED_FIFO or SCHED_RR are selected as Linux scheduling policy. The selection is done with the thread-policy target property that takes as values "rt-fifo", "rt-rr" and "normal" (for the default non-real-time Linux scheduler). Similarly, the cores target property restricts the number of cores that the application can use. In the future, these two target properties will become configuration properties specified in the main reactor with the '@' sign. The most appropriate syntax is yet to be defined.

A more thorough testing is in the works to make sure all corner cases are handled correctly.

fra-p added 15 commits August 6, 2025 21:24
1. Implemented collection of stats of all deadlines of an LF application
2. Implemented generation of an exponential-based priority assignment
function in the lf_start.c file (to be called by the runtime, not yet
modified)
3. Implemented a debug-only function that writes the deadlines of an LF
application to a text file
1. Implemented current thread priority set using POSIX APIs
2. Linux scheduling policy statically set to SCHED_FIFO (will need
target property to change it) and single-core mode only
3. Implemented call to Platform method in the scheduler to adjust thread
priority right before executing a reaction
1. Set maximum POSIX priority to TCP thread and to sleeping main thread
2. Added an exception when the runtime fails to set real-time scheduling
with POSIX APIs (likely due to lack of sudo rights)
1. Implemented the selection of Linux schedulers using the
"thread-policy" target property
1. Implemented the specification of core affinity for the main thread.
The target property specifies the number of cores that shall be
available to it, and the runtime assigns them starting from the highest
index.
1. Added void implementation of priority-setting APIs for non-POSIX
platforms (may be implemented in the future)
2. Fixed a bug causing maxwait not be checked when a federate has no
internal triggers before stop tag but receives inputs from other
federates
3. Improved debug output for STP violation errors to determine which
bundle causes the violation
4. Fixed a bug causing the priority function fail on assertion with only
one distinct deadline value
5. Priority function is now generated only when platform is Native
6. Priority function is now generated also with non-federated programs
1. The TCP thread has now always higher priority than the main thread,
even when this is sleeping. The sleeping main thread has got the second
highest priority. The range of priorities for the main thread when
executing reactions is now [1,97].
2.Fixed bug causing TCP messages not being sent immediately (Nagel's
algorithm is now disabled).
1. Implemented an API call to allow reactions set the maxwait parameter
of the decentralized coordination while the LF program executes.
1. Fixed a bug causing the code generator to crash when the LF program
did not contain any deadlines
2. Removed an old debug function that wrote to a file the LF deadlines
1. Fixed a concurrency bug possibly causing access by TCP thread to
not-yet-initialized platform object
1. Refactored the priority scheduler to comply with the
object-oriented-like design style
2. Created a ThreadedPlatform structure that models hw platforms with
threading capabilities
3. Created a PriorityScheduler structure that only works with
ThreadedPlatforms and that executes reactions with appropriate
priorities
1. Fixed a bug causing the priority function not being generated when no
deadlines were specified, but the scheduler was set to Priority.
1. Changed API function name to dynamically set maxwait from
lf_set_maxwait to lf_set_fed_maxwait
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 2, 2026

Benchmark results after merging this PR:

Benchmark results

Performance:

PingPongUc:
Best Time: 615.334 msec
Worst Time: 727.115 msec
Median Time: 677.175 msec

PingPongC:
Best Time: 176.757 msec
Worst Time: 201.395 msec
Median Time: 177.573 msec

ReactionLatencyUc:
Best latency: 7124 nsec
Median latency: 66397 nsec
Worst latency: 8953177 nsec

ReactionLatencyC:
Best latency: 15249 nsec
Median latency: 64599 nsec
Worst latency: 8493852 nsec

Memory usage:

PingPongUc:
text data bss dec hex filename
40868 800 9064 50732 c62c bin/PingPongUc

PingPongC:
text data bss dec hex filename
48098 894 384 49376 c0e0 bin/PingPongC

ReactionLatencyUc:
text data bss dec hex filename
26557 776 3112 30445 76ed bin/ReactionLatencyUc

ReactionLatencyC:
text data bss dec hex filename
43640 862 384 44886 af56 bin/ReactionLatencyC

Copy link
Copy Markdown
Collaborator

@edwardalee edwardalee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only been through part of this PR, but I found some issues, so I'm submitting a partial review in case these comments are useful. Most of the comments are insignificant... minor suggestions for documentation. But one is potentially a bug.

interval_t global_max_wait; // The global maximum wait time for remote input ports for this federate.

/**
* @brief Set the global maximum wait time for remote input ports for this federate.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @brief Set the global maximum wait time for remote input ports for this federate.
* @brief Set the global maximum wait time for this federate to assume remote input ports are absent.

* @param super The environment.
* @param max_wait The maximum wait time to be set.
*
* This function sets the global maximum wait time for remote input ports for this federate.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment is not needed. @brief is sufficient.

StartupCoordinator* startup_coordinator, ClockSynchronization* clock_sync);
void FederatedEnvironment_free(FederatedEnvironment* self);

#define lf_set_fed_maxwait(time) ((FederatedEnvironment *)env)->set_maxwait(env, time)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is the user-facing function, perhaps it needs documentation?

Comment thread include/reactor-uc/platform.h Outdated
Comment on lines +98 to +100
LF_SCHED_FAIR, // Non real-time scheduling policy. Corresponds to SCHED_OTHER
LF_SCHED_TIMESLICE, // Real-time, time-slicing priority-based policy. Corresponds to SCHED_RR.
LF_SCHED_PRIORITY, // Real-time, priority-only based scheduling. Corresponds to SCHED_FIFO.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LF_SCHED_FAIR, // Non real-time scheduling policy. Corresponds to SCHED_OTHER
LF_SCHED_TIMESLICE, // Real-time, time-slicing priority-based policy. Corresponds to SCHED_RR.
LF_SCHED_PRIORITY, // Real-time, priority-only based scheduling. Corresponds to SCHED_FIFO.
/** Non real-time scheduling policy. Corresponds to SCHED_OTHER in Linux. */
LF_SCHED_FAIR,
/** Real-time, time-slicing priority-based policy. Corresponds to SCHED_RR in Linux. */
LF_SCHED_TIMESLICE,
/** Real-time, priority-only based scheduling. Corresponds to SCHED_FIFO in Linux. */
LF_SCHED_PRIORITY

Comment thread include/reactor-uc/platform.h Outdated


/**
* @brief The number of cores of the hardware platform to use.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the default 0 mean? The default and its meaning should be documented.

abstract fun keepAlive(): Boolean

/**
* Collects all deadlines from federates, including nested reactors.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Collects all deadlines from federates, including nested reactors.
* Collect all deadlines from federates, including nested reactors.

* to find all reactions with deadlines.
*
* @param federates List of federates to collect deadlines from
* @return List of all deadlines found in the federates and their nested reactors
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense for the return value to be a Set rather than a List? A list has semantics to the ordering and has duplicate entries. Same for all related methods below.

Comment on lines +84 to +90
var delayExpr = instance!!.parameters.find { p -> p.lhs.name == parameterName }!!.rhs.expr
// Being a deadline, it must be a Time value
if (delayExpr is org.lflang.lf.Time) {
ASTUtils.toTimeValue(delayExpr).toNanoSeconds()
} else {
0
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look quite right to me. If delay is a parameter reference that is not overridden in the instantiation, then this will return 0 because delayExpr will be null. But the right return value is the default value of the parameter, which can be obtained using getDefaultAsTimeValue.

Also, rhs.expr could itself be a ParameterReference, in which case, the parameter referred to belongs to the parent Instantiation. I'm not sure how to get that parent Instantiation, however. I don't think it's even possible given the arguments. I think that instead of the instance argument, this needs to be an instances argument with type List<Instantiation>. This list needs to include all the Instantiations that contain this Instantiation. That list would need to be constructed in the collectDeadlinesFromNestedReactors below, which does the recursive visiting of instantiations.

Also, this looks to me like a function that should be provided in ASTUtils. I'm kind of surprised it isn't. I guess that is because there is an implementation of the same functionality in ReactorInstance.getTimeValue(Expression). reactor-uc is not using ReactorInstance or any of that infrastructure, however.

* @return List of deadlines found in this reactor
*/
fun collectDeadlinesFromReactor(reactor: Reactor, instance: Instantiation?): List<Long> {
val deadlines = mutableListOf<Long>()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a Set rather than a list?

* Returns stats on the deadlines of all federates (or the main reactor only if non federated)
* in the form of a list of three double values provided in this order:
* 1. the minimum deadline
* 2. the median deadline
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if you need the median deadline, then collecting the deadlines as a Set won't work. You need to know how many times a deadline appears to get the median.

fra-p added 2 commits January 23, 2026 20:39
1. Fixed bug that made the code generator crash when reaction deadlines
were parametrized and the default value was used
2. Fixed bug that did not allow the code generator to collect deadlines
of reactions that were parametrized with a parameter defined in the
parent reactor
1. Implemented exception handling when user does not provide a number
for the cores target property
2. Added and edited documentation for functions
3. Renamed enum types for thread scheduler property
4. Removed leading underscore from the name of functions that have
become part of the public interface
5. Implemented exception handling when user does not provide a valid
thread policy for the thread policy target property
@fra-p
Copy link
Copy Markdown
Collaborator Author

fra-p commented Jan 23, 2026

@edwardalee I fixed the bug with nested parameters and implemented most of your minor fixes. Thanks for your review!

Copy link
Copy Markdown
Member

@tanneberger tanneberger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firstly: Very Good Work! 🎉 👍 🔧

I left a lot of comments (sorry :D)

Couple of more big picture comments (it's okay if you hate me XD):

  • I would like to remove target properties from uC => turn them into annotations, this would allow us to have federates running on embedded (zephyr/riot) talking to a LinuxRT federate.

  • The code generator is right now is the ugliest part of uC: Put the Priority stuff into a PriortyScheduler.kt and lets add a good integration

  • I would recommend we add a PosixRT platform that implements the ThreadedPlatform, we still want that @edwardalee can run uC on his Macbook where there is no LinuxRT scheduler :D


StartupCoordinator* startup_coordinator; // A pointer to the startup coordinator, if the program has one.
ClockSynchronization* clock_sync; // A pointer to the clock synchronization module, if the program has one.
interval_t global_max_wait; // The global maximum wait time for remote input ports for this federate.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the max_wait here? Because the max_wait is normally stored inside FederatedInputConnections and can be configured per (Bundle/Connection)


typedef struct {
Platform super;
ThreadedPlatform super;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would require that all POSIX platforms are RT. I don't think this is wise, for example this won't work on MacOS. I would say we create posix and a posixRT platform.

#define PLATFORM_T PlatformPosix
#define MUTEX_T MutexPosix

lf_ret_t PlatformPosix_set_thread_priority(interval_t rel_deadline);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You dont need to declare the functions here, because the functions are only called through the function pointers stored in the objects. (Declaration is not needed)


bool _Scheduler_check_and_handle_stp_violations(DynamicScheduler *self, Reaction *reaction);
bool _Scheduler_check_and_handle_deadline_violations(DynamicScheduler *self, Reaction *reaction);
void Scheduler_pop_system_events_and_handle(Scheduler *untyped_self, tag_t next_tag);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the convention is <Class Name>_<method_name>

EventQueue* system_event_queue, ReactionQueue* reaction_queue, interval_t duration,
bool keep_alive);

bool Scheduler_check_and_handle_stp_violations(DynamicScheduler *self, Reaction *reaction);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here you can remove this.

* @return true if a violation was detected and handled, false otherwise.
*/
static bool _Scheduler_check_and_handle_stp_violations(DynamicScheduler* self, Reaction* reaction) {
bool Scheduler_check_and_handle_stp_violations(DynamicScheduler* self, Reaction* reaction) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the _ before the function name made clear that these functions where internal to this file.

}
}

LF_WARN(SCHED, "Event from %s trying to schedule at tag " PRINTF_TAG " past stop tag " PRINTF_TAG,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what do we need the FederatedInputConnection name? Except printing it :D

const char* federate_info = "unknown source";
if (event->trigger && event->trigger->parent) {
// Check if this is a federated input connection
if (event->trigger->type == TRIG_CONN_FEDERATED_INPUT) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and maybe put into an internal function for reuse ;D

Comment thread src/queues.c
}
}

validate(self->level_size[reaction->level] < (int)self->capacity);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this move?

super->super.run = PriorityScheduler_run;

Mutex_ctor(&super->mutex.super);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we made some changes in #288 to the scheduler especially when it comes to PollingNetwork channels.

@tanneberger tanneberger added this to the v0.1.0 milestone Feb 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Coverage after merging priority into main will be

50.86%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src
   action.c70.29%65.63%62.50%72.45%117–118, 131–132, 135–138, 141–142, 144–147, 149, 153, 156–159, 25, 42, 42, 44–46, 50, 52, 59–60, 67–69, 69, 69–72
   builtin_triggers.c90.91%70%100%96.77%14, 18, 40, 43
   clock_synchronization.c0%0%0%0%100–103, 105–106, 106, 106–109, 114–116, 116, 116–118, 120–122, 122, 122–124, 124, 124, 124, 124–126, 129, 13, 130, 132, 135, 137–139, 14, 141, 143, 143, 143–145, 147–149, 149, 149–151, 154, 156, 158, 16, 160–161, 161, 161–163, 163, 163, 166, 166, 166–167, 17, 170–175, 175, 175–176, 18, 180, 182–188, 188, 188–189, 19, 192, 195–199, 20, 200, 200, 200–201, 203, 205, 208, 210–214, 214, 214–215, 215, 215–224, 224, 224–225, 227, 23, 23, 23, 230–232, 234–236, 238, 238, 238–239, 24, 241, 244–245, 247–249, 25, 250–254, 254, 254–256, 26, 26, 26, 261–264, 264, 264–266, 268–269, 27, 273–275, 277–278, 278, 278–279, 279, 279–287, 287, 287–288, 290, 294, 297–298, 30, 301–310, 310, 310–311, 314, 317–320, 323, 323, 323, 323, 323, 323, 323, 323, 323–329, 33, 330–339, 34, 340–344, 347–348, 35, 350, 354–362, 364, 367, 367, 367–368, 372–376, 379, 379, 379–380, 383, 385, 40, 42, 44, 46, 49, 49, 49–51, 51, 51–52, 55, 58, 62, 64–66, 66, 66, 69, 69, 69–70, 73–79, 79, 79–80, 83, 89–90, 93, 95, 95, 95–97
   connection.c78.34%53.19%100%88.12%10, 10, 105, 111, 124–125, 139, 14, 14, 140, 146, 148–149, 151, 16–17, 21–22, 22, 22–23, 25, 27–28, 34, 48, 48, 48–49, 55, 61–63, 98
   environment.c94.12%83.33%100%100%16, 29
   event.c100%100%100%100%
   federated.c6.05%3.19%8.33%7.21%10, 101–102, 104–107, 11, 110, 112–118, 12, 120, 120, 120–121, 123, 123, 123, 123, 123–124, 124, 124–126, 128, 128, 128–129, 13, 130–131, 133, 136, 136, 136–138, 14, 142–143, 146–148, 148, 148–149, 149, 149, 15, 150–151, 153, 156, 158, 160–165, 168–169, 17, 17, 17, 170, 172, 172, 172, 174–177, 179, 18, 18, 18, 181, 181, 181–182, 184–185, 188–189, 19, 19, 19, 196–198, 198, 198–199, 20, 20, 20, 201–202, 204, 206, 206, 206–209, 21, 21, 21, 210, 210, 210, 210, 210, 210, 210–220, 220, 220–221, 223–229, 23, 230, 232–235, 239, 242, 242, 242–244, 246, 248, 25, 250–252, 252, 252, 252, 252–259, 26, 261–264, 264, 264–265, 267, 27, 270–273, 28, 281–283, 296–297, 297, 297–298, 298, 298–299, 299, 299, 30, 30, 30, 300, 300, 300–301, 301, 301–302, 302, 302–303, 303, 303, 305, 305, 305–306, 306, 306–307, 307, 307–308, 308, 308–309, 309, 309, 311, 32–33, 33, 33–34, 36, 38, 40, 40, 40–41, 45, 47, 51, 54–56, 58, 58, 58, 58, 58–59, 59, 59–60, 65, 65, 65–67, 71–72, 75–76, 78, 8, 80, 82, 84, 86, 88–89, 9, 90–97, 99
   logging.c88.52%83.33%100%89.36%25, 38–40, 47, 60–61
   network_channel.c69.23%62.50%100%70.59%40, 40, 40, 45–48, 57
   physical_clock.c89.61%83.33%71.43%93.10%26, 41, 57–58, 81, 83
   port.c75%42.86%100%91.67%10, 10, 10, 15, 15, 15–16, 22, 26, 31, 31–33, 33, 33–34, 46, 46, 46–47
   queues.c86.05%79.03%100%87.94%100–106, 129–131, 133, 140–141, 146, 162, 167, 173, 34, 34, 38–44, 69–70, 96, 96
   reaction.c71.31%56.52%100%78.87%15, 21, 28–30, 30, 30–32, 32, 32–33, 43, 46, 53–54, 54, 54–56, 56, 56–57, 72, 88–90, 90, 90–93, 93, 93–94
   reactor.c69.33%51.52%100%82.28%10, 101–102, 14–19, 22, 28, 30, 32–37, 37, 37–38, 38, 38, 43, 55, 58–59, 59, 59–60, 60, 60–61, 63, 77–78, 81–82,

@tanneberger tanneberger removed this from the v0.1.0 milestone May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants