Virtual threads#65
Conversation
| Thread.sleep(20); | ||
| } catch (InterruptedException e) { | ||
| log.error(getLogPrefix() + "Interrupted while waiting for ICE", e); | ||
| onConnectionLost(); |
There was a problem hiding this comment.
I added it on my own. I think if interrupted, then it is possible to terminate the connection.
| return socket; | ||
| } catch (SocketException e) { | ||
| log.error("Could not create socket for peer: {}", getPeerIdentifier(), e); | ||
| throw e; |
There was a problem hiding this comment.
If the socket has returned an error, then it doesn't make sense further. Because we are going to get a LocalPort, and the socket will be null.
|
|
||
| public void close() { | ||
| running = false; | ||
| refreshThread.interrupt(); |
There was a problem hiding this comment.
Why remove? If one of the methods in the while loop are blocking, the thread will keep being blocked.
There was a problem hiding this comment.
You are right, it will be blocked until the game ends. I returned it.
There was a problem hiding this comment.
It makes me wonder though, if then the running flag even makes sense 🤔
# Conflicts: # ice-adapter/src/main/java/com/faforever/iceadapter/IceAdapter.java # ice-adapter/src/main/java/com/faforever/iceadapter/debug/DebugWindow.java # ice-adapter/src/main/java/com/faforever/iceadapter/debug/DebugWindowController.java # ice-adapter/src/main/java/com/faforever/iceadapter/gpgnet/GPGNetServer.java # ice-adapter/src/main/java/com/faforever/iceadapter/ice/Peer.java # ice-adapter/src/main/java/com/faforever/iceadapter/ice/PeerIceModule.java # ice-adapter/src/main/java/com/faforever/iceadapter/rpc/RPCHandler.java
Brutus5000
left a comment
There was a problem hiding this comment.
I am just wondering if we don't shoot ourselves in the foot here.
From my understanding VirtualThreads were created to simplify the handling of asynchronous code. So using CompletableFuture.async methods handled in a one virtual thread per task-executor is the backwards compatible way to do it, but is there any benefit for new code to do so?
| CompletableFuture.runAsync( | ||
| () -> { | ||
| Thread.currentThread().setName("sendingLoop"); | ||
| sendingLoop(); | ||
| }, | ||
| executor); |
There was a problem hiding this comment.
Feels dangerous. If you don't pass a OneNewVirtualThreadPerTask executor, you might rename and block a shared pool thread.
Since the thread is a loop and not a one time task, why don't just spawn a virtual thread here and leave out the completable future?
|
|
||
| listenerThread = new Thread(this::listenerThread); | ||
| listenerThread.start(); | ||
| listener = CompletableFuture.runAsync(this::listenerThread, IceAdapter.getExecutor()); |
There was a problem hiding this comment.
Same here. Why not just spawn a virtual thread directly?
There was a problem hiding this comment.
You think that if I do this: "Thread.ofVirtual().start(this::listenerThread)". Will something change?
We are setting this up at the executor level.
There was a problem hiding this comment.
You have a reference to the thread so you can set the name, exceptionHandler and interrupt it.
| checker = CompletableFuture.runAsync( | ||
| () -> { | ||
| Thread.currentThread().setName(getThreadName()); | ||
| checkerThread(); | ||
| }, | ||
| IceAdapter.getExecutor()); | ||
| }); |
There was a problem hiding this comment.
This is a continuous task too, so just spawning a virtual thread seems easer.
There was a problem hiding this comment.
executor = Executors.newVirtualThreadPerTaskExecutor();
This means that all tasks where there is an executor will be on virtual threads.
There was a problem hiding this comment.
The executor is not defined in this class. So you couple this class to the implementation of another class.
But apart from that e don't make any use of the completablefuture. Since we don't run any follow-up operations on the future, just Thread.ofVirtual().name(...).uncaugtExceptionHandler(...) is easier to understand.
|
|
||
| listenerThread = new Thread(this::listener); | ||
| listenerThread.start(); | ||
| listener = CompletableFuture.runAsync(this::listener, IceAdapter.getExecutor()); |
There was a problem hiding this comment.
I would launch a virtual thread here too.
I think using an executor is still probably the right choice as that way in the future if we find out that in some places virtual threads are not the right choice or additional execution considerations need to be added we can easily do that by swapping out the executor. Although I do see your point on using the CompletableFuture API for infinite loop tasks and one shot / fire and forget tasks. In those cases I agree it might be best to just use the executor directly with executor.submit etc. |
And what is the difference between executor.submit and CompletableFuture.RunAsync(... , executor) ? We get the "Future" there and there. |
|
The main difference is the simplified API. Since only a normal future is returned it doesn't allow you to perform the async callback operations making it clear that the submitted task should be fairly standalone. |
That would not be my interpretation of reading such a signature. |
|
To the places where was "the thread = new Thread(...)" I returned the previous logic. In places where Thread was not controlled from close(), it will now be controlled from Executor. I suggest 2 options for controlling Threads:
|
| @UtilityClass | ||
| public class ExecutorHolder { | ||
| public ExecutorService getExecutor() { | ||
| return Executors.newVirtualThreadPerTaskExecutor(); |
There was a problem hiding this comment.
This creates a new executor every time. I guess we should keep one instance and return that all the time.
There was a problem hiding this comment.
private final ExecutorService executor = ExecutorHolder.getExecutor();
We create it once in the main class, and then distribute it to all other classes via IceAdapter.getExecutor()
There was a problem hiding this comment.
Ok, I guess that's not what Sheikah had in mind, but it's fine for now.
| } catch (InterruptedException e) { | ||
| log.info("Sending loop interrupted"); |
There was a problem hiding this comment.
Catching the interrupt without re-interrupting will clear out the .isInterrupted() state and block the while loop from ending
https://chatgpt.com/share/67502077-b55c-800e-99d3-f6ede4960126
There was a problem hiding this comment.
You would be right if the code had while (true).
But we have the use of while (!Thread.currentThread().isInterrupted()). After that, the 'while' ends and the thread stop doing work.
There was a problem hiding this comment.
If you catch the InterruptedException, !Thread.isInterrupted() returns always true.
There was a problem hiding this comment.
Yes, you're right, I forgot that this mechanism works like that. I'll fix it in the code now.
Virtualthreads don't like "synchronized". Therefore, I changed it to Lock.
Now all Threads are executed in Executor. And this Executor runs on virtual threads.
Switching to virtual threads requires 21 java.