WebSocket segfaults if connection to server drops out
LunaTheFoxgirl opened this issue · comments
Currently working on https://github.com/Inochi2D/vts-d
The example program segfaults if VTube Studio is closed and the WebSocket Server as a result stops sending data or if for some other reason data doesn't arrive properly.
In the log there's a WARNING: HTTPClientResponse not fully processed before being finalized
warning before the crash
Example Program
module app;
import vts;
import std.datetime;
import std.stdio : writeln;
import std.random : choice;
import vibe.core.core : sleep;
void main() {
VTSPlugin plugin = new VTSPlugin(PluginInfo("Test", "Me", null), "127.0.0.1");
plugin.login();
auto models = plugin.getModels();
do {
if (models.length > 0) plugin.tryLoadModel(choice(models).modelId);
sleep(5.seconds);
} while(plugin.isConnected());
plugin.disconnect();
}
LLDB Backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x10)
frame #0: 0x00000001005e6d70 vts-d`_D6object14TypeInfo_Class7getHashMxFNbNeMxPvZm + 12
vts-d`_D6object14TypeInfo_Class7getHashMxFNbNeMxPvZm:
-> 0x1005e6d70 <+12>: ldr x1, [x8, #0x10]
0x1005e6d74 <+16>: br x1
0x1005e6d78 <+20>: ret
vts-d`_D6object14TypeInfo_Class6equalsMxFIPvIQdZb:
0x1005e6d7c <+0>: ldr x0, [x1]
Target 0: (vts-d) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x10)
* frame #0: 0x00000001005e6d70 vts-d`_D6object14TypeInfo_Class7getHashMxFNbNeMxPvZm + 12
frame #1: 0x00000001005eb740 vts-d`_aaInX + 96
frame #2: 0x00000001000ab898 vts-d`_D4vibe4core14connectionpool__T16LockedConnectionTCQBx4http6client10HTTPClientZQBw6__dtorMFNfZv(this=0x00000001025b6300) at connectionpool.d:277:4
frame #3: 0x00000001000b2a88 vts-d`_D4vibe4http6client18HTTPClientResponse11__fieldDtorMFNeZv(this=0x00000001025bb000) at client.d:1091:2
frame #4: 0x00000001000b41cc vts-d`_D4vibe4http6client18HTTPClientResponse10__aggrDtorMFNeZv(this=0x00000001025bb000) at client.d:1091:2
frame #5: 0x00000001005f2ff8 vts-d`rt_finalize2 + 100
frame #6: 0x00000001005d348c vts-d`_D4core8internal2gc4impl12conservativeQw3Gcx5sweepMFNbZm + 1120
frame #7: 0x00000001005d031c vts-d`_D4core8internal2gc4impl12conservativeQw3Gcx11fullcollectMFNbbbbZm + 984
frame #8: 0x00000001005d065c vts-d`_D4core8internal2gc4impl12conservativeQw14ConservativeGC__T9runLockedS_DQCsQCqQCkQCkQCiQCtQBy18fullCollectNoStackMFNbZ2goFNbPSQEuQEsQEmQEmQEkQEv3GcxZmTQBbZQDsMFNbKQBnZm + 76
frame #9: 0x00000001005d88a4 vts-d`gc_term + 140
frame #10: 0x00000001005eed74 vts-d`rt_term + 72
frame #11: 0x00000001005ef2f4 vts-d`_D2rt6dmain212_d_run_main2UAAamPUQgZiZ6runAllMFZv + 168
frame #12: 0x00000001005ef070 vts-d`_d_run_main2 + 376
frame #13: 0x00000001005eeedc vts-d`_d_run_main + 148
frame #14: 0x0000000100030cc8 vts-d`main(argc=1, argv=0x000000016fdff4a8) at entrypoint.d:42:17
frame #15: 0x0000000101c79088 dyld`start + 516
It looks like this is using the overload of requestHTTP
that returns the response instead of the one that passes it as scope
to a callback. If that call is directly in your code, you could probably replace it with the scope
version to work around the crash.
For the crash itself, it looks like might be an order-of-destruction issue, where the GC destroys the referenced ConnectionPool
prior to the LockedConnection
that is as part of the HTTPClientResponse
. I'm not sure whether there is a simple fix (backwards compatible) within the library to avoid that, or if this is an obligation that needs to be passed to the user for now.
In the latter case, it would be necessary to catch the exception, either directly, or indirectly using a scope (failure)
guard, and call HTTPClientResponse.disconnect()
to ensure the response object frees any associated resources in time.
It looks like this is using the overload of requestHTTP that returns the response instead of the one that passes it as scope to a callback. If that call is directly in your code, you could probably replace it with the scope version to work around the crash.
Sadly no, I do not directly make the requestHTTP call, that is being done by connectWebSocket
Okay, then there is also an overload of connectWebSocket
that takes a WebSocketHandshakeDelegate
that internally uses the other connectHTTP
overload. I'll have a look at the exception handling in the web socket module later.
From what I can tell, I can't use the scope callback as the socket has to be a long lived object that is queried in a loop in things like games.
Having it long lived shouldn't be an issue, the task is light-weight and as long as it doesn't perform heavy computations, it shouldn't interfere with anything else in the main thread. The task could then be connected to other tasks/threads by establishing a two-way communication channel, which, in contrast to std.concurrency
, is strongly typed and doesn't allocate for each message.
I would need to figure out how to start/stop it on command, seems difficult considering how scope will destroy the object after it exits the scope
Channel
has a close
method and is reference counted (a copy would exist on both sides), so that could be used to safely communicate the end of the connection/scope.