swimos / swim

Full stack application platform for building stateful microservices, streaming APIs, and real-time UIs
https://www.swimos.org
Apache License 2.0
488 stars 41 forks source link

JoinValueLane.get() will not work most of the time #107

Open brohitbrose opened 1 year ago

brohitbrose commented 1 year ago

TODO: instructions on how to recreate the following.

There are two types of agents: the bottom-level SiteAgent (many instances) and a CellSitesBugAgent (singleton) that aggregates all SiteAgents. The CellSitesBugAgent has two JoinValueLanes that subscribe to the exact same ValueLane in each SiteAgent . Only difference is that one is JoinValueLane<String, Summary> (Summary is a trivial pojo) and the other is <String, Value>.

The Summary-typed JVL has logging upon didUpdate(), and it works correctly. This is just to prove that the didUpdate() is not a problem. You may want to disable this logging when debugging.

The problem: the CellSitesBugAgent also has a timer that, every 10 seconds, outputs the result of calling get("/site/83") against both JoinValueLanes (which are named sites and sitesStructure). The output looks something like.

/sitesBug#timer: sites contains null
/sitesBug#timer: sitesStructure contains Record.of(Attr.of("downlink", Record.of(Slot.of("node", "/site/83"), Slot.of("lane", "toPublish"))), Attr.of("summary"), Slot.of("severity", 0.0), Slot.of("mean_ul_sinr", 4), Slot.of("rrc_re_establishment_failures", 3), Slot.of("recorded_time", 699574224))

The null output comes from the Summary-typed JoinValueLane, and it results from failing to cast the lane-internal swim.structure.Value into a Summary. If we log the underlying Value (by using our Value-typed JoinValueLane), we see that a @downlink({node:...lane:...}) has been tacked onto it; this is the second line of the above output.

The reason the title of this issue says "most of the time" is because this can be circumvented with an ugly hack. JoinValueLanes delegate to swim.structure.Forms, so we can use a custom Form whose cast method accounts for this for each type. This is a terrible idea, but technically feasible.

brohitbrose commented 1 year ago

Reimplimenting JoinValueLaneView#get(Object) as

final Value structure = this.laneBinding.get(this.keyForm.mold((K) key));
return structure.isDefined() ? this.valueForm.cast(structure) : this.valueForm.unit(); // or null

and suppressing "unchecked" warnings seems to work. I tried this out because I noticed that the JoinValueLaneModel class (of which this.laneBinding is an instance) actually implements the trimming of the @downlink correctly. I don't know if this is a threadsafe or efficient call though, and it seems to break the intention of having a separate MapData<K, V> dataView as an instance field. However it's worth noting that JoinValueLaneView#remove also delegates to this.laneBinding class and is implemented similarly.

At any rate, many of the other JoinValueLaneView methods that utilize dataView (e.g. containsValue) are also broken in the current implementation.