Skip to content

Add support for inherited sockets #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 21, 2024

Conversation

Cu3PO42
Copy link
Contributor

@Cu3PO42 Cu3PO42 commented Nov 17, 2024

This PR implements support for using inherited sockets instead of opening a new socket on a specified port. The primary motivation for this feature is socket activation. It lets the Robocode server be started on demand as soon as a bot, controller or observer conects to it.

I also intend to use this feature to make orchestration of the server easier without dealing with port conflicts for my battle runner.

Finally, this also enables (indirect) IPv6 support by opening a v6 socket and passing it.


Currently, this relies on an unreleased version of Java WebSocket, so I have marked this as a draft. I'll update the PR once v1.6 of the library is released.

@flemming-n-larsen
Copy link
Contributor

@Cu3PO42 Really nice. I will try this out to see how it works. And yes, we might need to wait with releasing it until the unreleased version of Java WebSocket becomes a real release, as it might have some unwanted side-effects. Let's see. 🙂

@Cu3PO42
Copy link
Contributor Author

Cu3PO42 commented Nov 17, 2024

Thanks for taking a look! The easiest way to test this is to run systemd-socket-activate -l 7654 --inetd java -jar path/to/server.jar -p inherit on a Linux system with systemd. Passing sockets is supported on other OSes as well, but you would probably need to implement your own helper to test it.

The expected behavior when using the above command is that the server should launch on port 7654 as soon as you start a bot, for example. Other than that, it should work exactly the same as if you had launched it the conventional way.

@Cu3PO42 Cu3PO42 force-pushed the feat/inherited-sockets branch from c4b6102 to 907f949 Compare November 17, 2024 23:08
@flemming-n-larsen
Copy link
Contributor

@Cu3PO42 I have had a first look at the changes you made, and it is looking good to me.
And I am very pleased that you also updated the description of the port parameter etc. so it fits the new feature. 👍

Since I am working mostly on a Windows 11 machine, I will setup Ubuntu on my system (again) so I can try out how it works. 🙂 I am pretty sure it will work just fine.

While running the code locally, I found another issue with the server that I will try to fix. I hope it will not impact this PR.

@Cu3PO42
Copy link
Contributor Author

Cu3PO42 commented Nov 21, 2024

Thank you! This feature should work just fine inside WSL2, so you might get away with simply installing that. If I recall correctly, running wsl on the command line should prompt you to install any necessary components.

@SirStone
Copy link
Collaborator

Just a question about this implementation. Would be possible that socket activation could allow a bot to be reloaded without loosing the connection?
I was thinking that would be really nice to be able to restart the bot, after changes to it's code, without requiring to stop the bot->stop current match->add the bot just connected again->start the match at every little change

@Cu3PO42
Copy link
Contributor Author

Cu3PO42 commented Dec 1, 2024

Would be possible that socket activation could allow a bot to be reloaded without loosing the connection?

Not really, no. You could use the same mechanism to pass a socket from one generation of your bot to the new version after you make some changes, but an open socket isn't all that useful. You would need an open WebSocket connection, which has a bunch of associated state. It is possible to also preserve that when execve-ing a new binary, but it would be very complicated.

A much more realistic approach for your scenario would be to write a thin proxy for the connection between bot and server. That should be possible with relatively little effort and honestly sounds like a good idea. This would be entirely unrelated to the changes introduced here, though.

@SirStone
Copy link
Collaborator

SirStone commented Dec 1, 2024 via email

@Cu3PO42 Cu3PO42 force-pushed the feat/inherited-sockets branch from 907f949 to d7565ce Compare December 4, 2024 19:17
@Cu3PO42 Cu3PO42 force-pushed the feat/inherited-sockets branch from d7565ce to 25ad795 Compare December 8, 2024 22:45
@marci4
Copy link

marci4 commented Dec 16, 2024

@Cu3PO42 Really nice. I will try this out to see how it works. And yes, we might need to wait with releasing it until the unreleased version of Java WebSocket becomes a real release, as it might have some unwanted side-effects. Let's see. 🙂

I released 1.6.0 yesterday :)

@flemming-n-larsen
Copy link
Contributor

@marci4:

I released 1.6.0 yesterday :)

Nice! Thank you for the update/notification! 😊👍

@Cu3PO42
I will update the dependency. Test it out, and merge the pull request later today. 😊

…nary dependency to real version 1.6.0 just released.
… and exit, if the inherited channel is not supported to avoid a stack trace most users don't understand.
@Cu3PO42
Copy link
Contributor Author

Cu3PO42 commented Dec 16, 2024

Thank you both! I'm excited to see this merged :-)

@flemming-n-larsen
Copy link
Contributor

flemming-n-larsen commented Dec 16, 2024

@Cu3PO42

So now I want to test this feature? 🙂
I guess it should be easy on Linux and macOS, but perhaps not Windows?


EDIT 1: I found out how to set up the server with systemctl on UBuntu Linux (WSL on Windows). So I will write here how to set it up and run it. Note that it is currently giving an error when called for "argument 0", so I will need to fix this as well. Just to let you know.


EDIT 2:

Here's an a guide for how to setup the socket activation (work in progress).

Guide to Systemd Socket Activation for Robocode Server

Socket Unit File Setup

Create a socket unit file at /etc/systemd/system/robocode.socket:

[Unit]
Description=Robocode Tank Royale Socket

[Socket]
ListenStream=7654

[Install]
WantedBy=sockets.target

Important

This configuration uses port 7654, which is the default for Robocode.

Service Unit File Setup

Create a service unit file at /etc/systemd/system/robocode.service:

[Unit]
Description=Robocode Tank Royale Server
Requires=robocode.socket
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/bin/java -jar /opt/robocode/robocode-tankroyale-server-0.28.0.jar --port=inherit
WorkingDirectory=/opt/robocode/
TimeoutStartSec=30
StandardInput=socket
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Important

Assumes Robocode server jar is located in /opt/robocode and using version 0.28.0. Adjust paths and version as needed.

The StandardInput=socket makes it possible for Java to passing the socket and make it available with the java.lang.System.inheritedChannel() method required by the Server.

You might need to add add a User=<some-user> and Group=<some-group> under [Service] to access the /opt/robocode directory.

Socket Management Commands

Enable the socket:

sudo systemctl enable robocode.socket

Disable the socket:

sudo systemctl disable robocode.socket

Start the socket:

sudo systemctl start robocode.socket

Stop the socket:

sudo systemctl stop robocode.socket

Verification and Monitoring

Check service port availability:

ss -tuln | grep 7654

Expected output when service is running:

tcp   LISTEN 0      4096                *:7654            *:*

View service logs (with real-time monitoring):

journalctl -u robocode.service -n 50 -f

Restarting the Service

sudo systemctl daemon-reload
sudo systemctl restart robocode.service

Reading Service Status

sudo systemctl status robocode.service

Expected output:

● robocode.service - Robocode Tank Royale Server
     Loaded: loaded (/etc/systemd/system/robocode.service; disabled; preset: enabled)
     Active: active (running) since Fri 2024-12-20 22:19:16 CET; 4min 23s ago
TriggeredBy: ● robocode.socket
   Main PID: 4778 (java)
      Tasks: 34 (limit: 19134)
     Memory: 85.7M ()
     CGroup: /system.slice/robocode.service
             └─4778 /usr/bin/java -jar /opt/robocode/robocode-tankroyale-server-0.28.0.jar --port=inherit

@Cu3PO42
Copy link
Contributor Author

Cu3PO42 commented Dec 20, 2024

Hi! I am so sorry, I have been swallowed up by life and work for the last couple of days. I did some research into socket inheritance on Windows and it is possible, but much more complicated than on Unix systems. It also requires a different, much more involved form of cooperation and does not seem to work with Java at all (without ugly JNI hacks at least).

You are on the right track with your current configuration. There is a somewhat easier way to test this by simply running systemd-socket-activate -l 7654 --inetd java -jar path/to/server.jar -p inherit. This does not require setting up any of the files that you did, but is not persistent.

As for the issue you are currently experiencing, it is because Java follows an older convention for passing sockets than systemd defaults to. You need the following in your service file:

[Unit]
Description=Robocode Tank Royale Service

[Service]
ExecStart=/usr/bin/java -jar /opt/robocode/robocode-tankroyale-server-0.28.0.jar --port=inherit
StandardInput=socket

@flemming-n-larsen
Copy link
Contributor

No need to be sorry. Everybody are busy these days before Christmas, myself included. 🙂

I found out yesterday, that the StandardInput=socket is fixing the issue. So you are definitely right that this was the reason why System.inheritedChannel() became null when it was missing.

I have updated the guide and I will include this in the documentation when we do the 0.28.0 release soon with this feature included. 😊

I still need some minor adjustment before that. But I want to release soon!

@flemming-n-larsen flemming-n-larsen merged commit 936d197 into robocode-dev:main Dec 21, 2024
@flemming-n-larsen
Copy link
Contributor

@Cu3PO42
Your work is now included in release 0.28.0. Thank your for bringing this cool feature to Robocode. 💪

@marci4
Thank you for for providing and maintaining the excellent Java WebSocket library, making WebSockets accessible for applications like Robocode without the need of running a "real" server. 🙏

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.

4 participants