Common Port Design Patterns
This guide will walk you through several common patterns using ports in F Prime.
You should be familiar with Ports, Components, and Topologies in F Prime as well as have a basic understanding of modeling in FPP. To build an understanding of these concepts, you should consider running through our Hello World Tutorial.
Common Port Patterns: 1. Synchronous Get Ports 2. Callback Ports 3. Parallel Ports 4. Synchronous Cancel
Synchronous Get Ports
Synchronous get ports are used to return data immediately from a component. These ports allow for the immediate query of data between components in an F Prime topology.
These ports can provide function-call like behavior between components that are connected together in an F Prime topology while still working within the bounds of the F Prime component architecture.
An example of synchronous get ports can be seen in the PrmDb component.
sequenceDiagram
Calling Component->>+Receiving Component: getTemperature
Receiving Component-->>-Calling Component: Synchronous Response
Warning
When the value needed requires non-trivial work to compute a Callback Port should be used instead.
Synchronous get ports maintain the architectural distinction between components while facilitating the return of data possible like in a standard function call. However, since these port calls execute on the thread of the caller, they should not perform substantial work as that would slow down the calling component..
Synchronous get component port instances typically use a name of the form "get
Note
Ensuring the work performed when in the synchronous handler is as minimal as possible is crucial to prevent delays from propagating back to the caller.
Implementation
Synchronous get ports are implemented using a port type with a return value:
Component implementations will use a port instance of either of the synchronous port kinds (sync
or guarded
) and the "get" naming scheme described above.
Finally, the port handler should be minimal often returning just returning the value of a member variable previously set.
Note
This snippet used the guarded
kind to ensure data member protection provided through the guarded
port's mutex.
Conclusion
Synchronous get ports are used to return values from one component in the F Prime topology to another when little or no work is necessary to produce that value.
Callback Ports
The callback port patten is used to separate a request port invocation from a following response port invocation allowing the requestor to return to other work while the request is completed. The ports used may be of any type and may contain port arguments to communicate results.
An example of this pattern can be found as part of the Manager/Worker interaction seen in F Prime examples.
sequenceDiagram
Requesting Component->>+Receiving Component: requestTemperature
Receiving Component-->>Requesting Component:
Receiving Component->>-Requesting Component: recvTemperature (Asynchronous Response)
Callback ports use two port connections between two components. The first port connection is an output port from the requester and an input to the receiver. The second port is an output of the receiver and an input back to the requester.
Command dispatch in F Prime uses the callback pattern. The command dispatch port is invoked from the dispatcher (callback requester) to a component (callback receiver). The component will callback using the command completion port sending the success/failure status of the command execution.
Callback port instances receiving the results of a request should distinguish themselves from synchronous get ports by using naming of the form "recv
Callback ports can be of any instance kind (sync
, guarded
, async
) but are most commonly seen with async
request ports as these cannot return a value upon invocation like synchronous get ports.
Implementation
The requestor side of the callback port patten instantiates an output
request port instance of any port type, and an input
receive port instance of any type. In the example below, Fw.Signal
is used as the request type, and response port types, however; any port type may be used.
component MyRequestor {
@ Start long-running request
output startRequest: Fw.Signal
@ Receive data from long-running request
guarded input recvResponse: Fw.Signal
}
The receiver contains the same ports with opposing directions.
component MyReceiver {
@ Start long-running request
async input startRequest: Fw.Signal
@ Send data from long-running request
output sendResponse: Fw.Signal
}
Finally, in the system topology the two component instances are cross-connected.
connections CallbackPattern {
requestor.startRequest -> receiver.startRequest
receiver.sendResponse -> requestor.recvResponse
}
Conclusion
The callback port patten is used to separate a request from the response allowing the requestor to return to other work while the request is completed.
Parallel Ports
Parallel ports are can be used to manage a set of callback ports and other sets of ports that attach to a set of external components. The advantage of the parallel port pattern is that it makes managing the call-response across this set of components easier by ensuring that ports are treated as parallel arrays. FPP modeling provides checks for parallel ports as well.
An example of parallel ports is the Command Dispatcher where compCmdSend
is parallel to compCmdReg
allowing correlation between command sending and registration.
sequenceDiagram
Caller->>+Receiver 1: outgoing [0]
Receiver 1->>-Caller: returning [0]
Caller->>+Receiver 2: outgoing [1]
Receiver 2->>-Caller: returning [1]
By connecting a given component from the set of external component using the same index across ports the source component implementations can correlate the messages by port index.
Implementation
In order to implement the parallel-port pattern, the user should define the ports using like-sized port arrays. Parallel ports may use any kind of port (sync
, async
, guarded
) and any port types as long as the port instance are arrays of the same size. It is best practice to define a constant to hold the dimension of the parallel port array.
The FPP modeling language provides a mechanism to automatically check parallel ports. This is called matched ports. Using match
and with
you can establish a parallel relationship between two port arrays. This will yield an error if matched ports are not hooked up in parallel.
In the snippet below, we implement outgoing/incoming ports using parallel ports.
@ Parallel Port Array Count
constant PARALLEL_PORT_COUNT = 10
@ Outgoing parallel port
output port outgoing: [PARALLEL_PORT_COUNT] Fw.Signal
@ Incoming Parallel Port
async input port incoming: [PARALLEL_PORT_COUNT] Fw.Signal
@ Establish a model-checked parallel relationship
match incoming with outgoing
In the system topology, components should be wired to the same port index on the outgoing and incoming sides.
connections ParallelPorts {
myComponent.outgoing[0] -> comp0.in
myComponent.outgoing[1] -> comp1.in
comp0.out -> myComponent.incoming[0]
comp1.out -> myComponent.incoming[1]
}
Notice how comp0
in this snippet is is wired to myComponent
's ports always using the 0th index. The same is true for comp1
on the 1st index. This allows the component to correlate the remote component via index.
In the C++ implementation this would look like:
// Send a message to comp0
this->outgoing_out(0);
// Send a message to comp1
this->outgoing_out(1);
void incoming_handler(FwIndexType portIndex) {
switch (portIndex) {
case 0:
// Handle message from comp0
break;
case 1:
// Handle message from comp1
break;
}
}
Note
comp0
and comp1
could be any components. The important part is that a given component's ports (outgoing and incoming in this example) always use the same index for the same remote component.
Conclusion
Parallel ports are useful in correlating port calls to a set of components using the port index. Parallel ports can be automatically checked using match A with B
syntax in FPP.
Synchronous Cancel
The synchronous cancel pattern is used to stop work running inside an asynchronous port handler.
Asynchronous components performing long-running work inside an async
port handler or async
command handler may need to stop that work before it has completed. However, another async
message would be queued behind the long-running work and would be unable to halt the ongoing work.
This situation can be remedied by using a sync
port call to set a cancel flag, which the long-running work can poll to determine if the work should be stopped early.
An example of this pattern can be found as part of the Worker Component in F Prime examples.
sequenceDiagram
Requestor->>+Long Runner: Start Work (Asynchronous)
Requestor->>Long Runner: Cancel (Synchronous)
Long Runner-->>Requestor:
Long Runner->>-Requestor: Work Canceled (Callback)
Note
This pattern works equally well with sync
commands allowing ground operators and sequences to cancel long-running work instead of other components.
Note
The thread invoking cancel and the thread performing work are on different threads. Cancel flags or other shared data items must be operated on atomically.
Implementation
Implementation of this pattern requires a synchronous port, perhaps using the Fw.Signal
type:
@ Port used to cancel long-running work
sync input port cancel: Fw.Signal
@ Example port performing long-running work to be canceled
async input port exampleRun: Fw.Signal
The component implementation class must provide a member variable (or other mechanism) to set synchronously when cancel is invoked. Here we use std::atomic
to guarantee atomicity, however; a plain member variable could also be used in conjunction with Os::Mutex
to also ensure atomicity.
The port handler of the cancel port then atomically sets a cancel flag to indicate that the long running work should stop.
void cancel_handler() {
// std::atomic ensures an atomic set of the cancel flag
this->m_cancel = true;
}
Finally, the long-running work must occasionally poll to see if cancel is set. This is shown below using a while
loop . Again, use of std::atomic
or Os::Mutex
is critical to ensure this read (and the above write) are safe as the read/write occur on different threads.
void exampleRun_handler() {
// Reset cancel flag before doing work
this->m_cancel = false;
// Loop until the work is done or the cancel request was received
while (not work_done && not this->m_cancel) {
... do work ...
}
}
Conclusion
The synchronous cancel pattern is used to stop work running inside an async
handler. It is performed by atomically setting a cancel flag in a sync
handler and polling that flag during the work running in the async
handler.
Conclusion
You should now be able to understand and apply common port patterns in F Prime.