lf-lang / lingua-franca

Intuitive concurrent programming in any language
https://www.lf-lang.org
Other
228 stars 62 forks source link

Network reactions create cyclic dependencies for banks #1201

Open Soroosh129 opened 2 years ago

Soroosh129 commented 2 years ago

A cycle is incorrectly detected in the following program:

target C;

import Count from "../lib/Count.lf"
import TestCount from "../lib/TestCount.lf"

reactor PassThrough {
    input in:int;
    output out:int;
    reaction(in) -> out {=
        SET(out, in->value);
    =}
}

federated reactor {
    count = new Count()
    passThroughs = new[10] PassThrough()
    destination = new TestCount()

    count.out, passThroughs.out -> passThroughs.in, destination.in
}

diagram

The cycle goes away if the federated reactor is changed to main.

The reason behind this is a bit complicated to explain. In short, the generated network reactions (see #608) for the input and output ports of each of the 10 PassThrough federates depend on all the other PassThrough federates, because there is no way to use individual bank members as triggers for reactions.

For example, the following is not possible:

target C;
reactor Contained {
    input in:int;
    output out:int;
}
main reactor {
    c = new[10] Contained()
    reaction(c[0].out) -> c[1].in {==}
}

Instead, all generated reactions will look like reaction(c.out) -> c.in {==} creating dependencies that result in a cycle.

edwardalee commented 2 years ago

Maybe this is another place where annotations could save the day. Suppose we add an annotation that is an assertion that the reaction only reads or writes to one of the bank members.

Soroosh129 commented 2 years ago

Maybe this is another place where annotations could save the day. Suppose we add an annotation that is an assertion that the reaction only reads or writes to one of the bank members.

That is an interesting idea. I think this would solve the problem.

Still, we could also conceivably make each bank member addressable in reactions' signature (and potentially in connections).

cmnrd commented 2 years ago

because there is no way to use individual bank members as triggers for reactions.

There actually is a way:

reactor NetworkReceive {
  output o: Type
  reaction (...) -> o {=
    /* ... */
  =}
}

reactor NetworkSend {
  input i: Type
  reaction (i) -> ... {=
    /* ... */
  =}
}
main reactor (width: size_t(10)) {
   c = new[width] Contained()
   s = new[width] NetworkSend()
   r = new[width] NetworkReceive()

  r.o -> c.in;
  c.out -> s.i;
}

By using reactors with a single reaction we can put them in a bank and thus have different reaction instances for the different instances of Contained (or PassThrough). Or is there some additional constraint that I am missing?

Soroosh129 commented 2 years ago

This is a great idea!