Skip to content

Events support: Java API#124

Open
dhouck wants to merge 6 commits intoNorth-Western-Development:1.20.1from
dhouck:events-java-api
Open

Events support: Java API#124
dhouck wants to merge 6 commits intoNorth-Western-Development:1.20.1from
dhouck:events-java-api

Conversation

@dhouck
Copy link

@dhouck dhouck commented Feb 22, 2026

Description

This is the Java side of my ongoing project to finish the partial support for events and subscriptions thatʼs in the code.

The main problem was that the existing code does not allow more than one message in the queue for the VM to receive, and would raise an exception if there was more than one, which could easily crash the server. My initial thought was to still have the limit but discard extra messages, but often more than one event at a time is useful (especially with the redstone interface block, given the way that redstone dust has excessive amounts of block updates when it powers down), so I did a more complicated thing instead.

There are a few other problems with the Java side of events that I also fixed: unsubscribe didnʼt work with ObjectDevices, the way the RPC bus is hooked up to the VM causes extra garbage the bus didnʼt know how to handle (which I have a hacky fix for and a couple ideas about better fixes which can be implemented separately later), the bus would ignore devices which only have events and not methods, and finally there was a chance messages would be silently dropped if there were too many events.

Other work

I am also working on making the Python library better (I can maybe work on the Lua library after that but I do not know Lua nearly as well), but that will be a different PR. The goal is that as long as subscriptions are not being used, everything here stays exactly the same, and the existing library can still work.

My existing Python PR, #122, is for things that make sense even without events, even if most of it is unlikely to come up; the future PR will have things like Device.subscribe and Device.unsubscribe methods.

I also have plans to improve the redstone interface events in a few ways, but that is also a separate PR; for now it generates many redundant events and the side identification is global for those events even though itʼs local for all the method calls.

The medium-term solution to the echo problem requires adding a program to the Minux rootfs-overlay and setting it up to launch at system startup; I have most of the program written but have not finished it.

If the long-term solution to the echo problem is adopted, it will require a change to the Sedna VirtIOConsoleDevice class; I have a proof-of-concept change there but it needs work. It will also require any external projects which assume the location of the bus devices to be updated; with the current hacky solution or the medium-term less-hacky one, projects will only need to be updated if they want to support events.

Specific things to call out

This changes the on-the-“wire” format in two ways, both of which should be mostly backwards compatible but which should be made explicit:

  • All events sent from the bus to the VM now include an extra seq field, in addition to type and data. This is a long which increments for each message, making it easy to see when messages are missed. Currently no library checks it, of course, but this could be very useful if it comes up.
    • Should be fully backwards-compatible; I expect all existing libraries ignore it by default. The built-in micropython and lua libraries ignore it in all cases I can find, with both experimentation and checking the code. The only other implementation I know of is 27factorial/oc2-hlapi, which I havenʼt tested but based on reading the code should silently ignore the extra field. I can check other libraries if anyone is aware of any, but I expect most will also silently ignore unknown fields.
    • The VM can also optionally send a sequence number to the bus, which is parsed and silently ignored if present, and set to -1 and silently ignored if absent.
    • Just in case, I confined this to the last commit; if this is undesired I can easily remove it and leave the version without sequence numbers.
  • Both subscribe and unsubscribe methods from the VM now get a response like every other VM-to-bus message does; previously they only got a response on error. The success message is an empty message of the same type, which somewhat mirrors the way list and methods messages work now
    • Should be backwards compatible for anyone who doesnʼt use the existing subscription functionality
    • Will need a small change if anyone does, but given how existing subscriptions tend to crash the game I donʼt expect anyone will mind.

Also, I am not used to Java and may have made bad assumptions, especially around concurrency. I donʼt think I introduced any concurrency issues, but that should be double-checked. Iʼm particularly unsure about RPCDeviceBusAdapter::reset; it looks like that might have had issues already, although I havenʼt caught it acting up, so Iʼm not sure if that is handled properly or if I might have made it worse.

@dhouck
Copy link
Author

dhouck commented Mar 1, 2026

I have what I believe to be a good solution to the potential for missing events, with a fully backwards-compatible HLAPI change (adding an extra field besides type and data to the JSON, not expected to be sent from the VM; existing libraries will just ignore it). A library could then check that it never skips anything and raise a warning if it did. This is far easier than my other idea of injecting an error into the message stream (when thereʼs already not much space in the buffer) and it gives more information to the VM.

The other TODOs are at least partly blocked on my understanding of RPCDeviceList working the way I think.

I hope to look into both within the next few days.

@dhouck dhouck force-pushed the events-java-api branch from aa806d4 to e1eac58 Compare March 3, 2026 06:33
@dhouck dhouck marked this pull request as ready for review March 3, 2026 23:10
@dhouck
Copy link
Author

dhouck commented Mar 4, 2026

Oh, as per the comment added in the third commit, there can be problems in crmode, if subscriptions are still active when the bus FD is closed. I donʼt think any userspace API uses that, and again it wonʼt cause any issues if nothing is subscribed to, but worth an explicit mention here.

@dhouck
Copy link
Author

dhouck commented Mar 10, 2026

(Fixed some whitespace in the latest commit)

@dhouck
Copy link
Author

dhouck commented Mar 10, 2026

Added 571f7f7, because it makes more sense for the bus (which already has the gson object and already does conversion for everything else including method results) to convert event data to JSON. As I understand it (and this is certainly the case in my tests), the gson library will just do the right thing if the passed object is already a JsonElement, so this should be fully backwards compatible unless someone is implementing IEventSink outside the mod.

@dhouck
Copy link
Author

dhouck commented Mar 18, 2026

Update: the issue is fixed but see below for discussion of the fix.

Do not merge; I found a bug that happens specifically when upgrading from before this to after (because null is no longer a valid value for receiveBuffer but itʼs what youʼll usually find when upgrading).

I think I have a fix, and itʼll be tested in a few minutes.

dhouck added 6 commits March 18, 2026 17:06
This makes events almost fully supported.  However, there is still a bug
with unsubscribing and event-only devices without methods are not yet
supported.

This change requires switching to a single buffer, like with the
transmitBuffer, although this one occasionally needs to be reallocated
to support large messages.  The max message size was previously only
enforced for messages from the computer to the bus, but messages from
the bus to the computer could be arbitrarily large if, for example,
there were a lot of devices hooked up and you made a `list` call.  Now
messages of arbitrary size are still supported, but sending lots of
small messages without clearing the buffer (as might happen if the
computer subscribes to something then never reads the events) won't grow
memory use indefinitely.

Because ByteBuffer isn't thread-safe this also synchronizes the reading
and writing to avoid issues; since you can't safely re-assign the
locking object this also requires implementing a new object as the lock
for the receive buffer.  It is possible that switching to a different
data structure would avoid this, but none seemed suitable given the need
to occasionally grow for large messages but not to grow automatically
for lots of small messages.
This allows us to fix a bug with unsubscribing from ObjectDevices, and
to have an RPCDevice with no methods which only generates events.

For most RPC devices, either they are an event source, or they are not,
and `device instanceof RPCEventSource source` either works or gives
null, as appropriate.  But in some cases, such as for. ObjectDevice, the
RPCEventSource and the RPCDevice are different objects, so this can be
overridden.

Related to this, unsubscribing was broken for ObjectDevices, because
RPCDeviceBusAdapter::unsubscribe assumed the simpler line was
sufficient.  Now both subscribe and unsubscribe use the asEventSource
method, and are otherwise simpler and less likely to get out of sync.

Additionally, this makes the check of whether a DeviceList supports
subscription a single line, which makes it easy to check when populating
all the devices on the bus, and lets us easily support event-only
devices with no methods.  This should be rare (even a device primarily
intended to present events can have methods for checking state), but
there are a few use cases.

To the extent subscriptions were previously supported, this is a
backwards-compatible change; if something worked as an event source
before (with or without having issues with unsubscribing), it still
should.
We already use the bus's gson instance for method results; we should for
event data also.
When a virtio console device that acts as a TTY, like the current RPC
bus implementation, is opened, the Linux kernel resets termios and
re-enables echo.  This is usually not a problem, unless a device is left
subscribed when the bus FD is closed and tries to send a message, which
will then be echoed back.

This is a hacky workaround that mostly detects and avoids the problem,
but hopefully will be replaced by at least one of the two better
suggestions.  As part of that hope, this also removes mention of
/dev/hvc0 except as necessary for existing scripts, to make it easier to
move in the future.
Previously if a message was dropped, there was no way to know.  This
gives every message from the bus to the VM a sequence number; if any is
ever skipped, that tells the VM that some messages were not received.
This should rarely happen unless there is a flood of events, or a few
very large events, but if it does happen it's good to be able to know.

So far, no userspace library actually handles these sequence numbers,
but as far as I can tell none of the existing libraries are broken by
them either.
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.

1 participant