RejectedSoftware Forums

Sign up

Custom shutdown on SIGINT

Hello,

I am writing a network client program using Vibe on Linux. The program needs to log out from the server on shutdown, when SIGINT is raised (like pressing Ctrl+C). In order to do so, I tried to use createFileDescriptorEvent with a Linux signalfd.
What happens is that FileDescriptorEvent.wait() returns immediately rather than waiting for the signalfd to be ready for reading. I traced it down to a recv(MSG_PEEK) call in eventcore. This call fails with ENOTSOCK.
My question now is, in order to understand how non-socket file descriptors could be supported: What is the purpose of this call?

Thanks,
David

Re: Custom shutdown on SIGINT

I got it working, resorting to the socket-to-self trick. Still I am interested if non-socket event sources could be supported as well.

Re: Custom shutdown on SIGINT

On Fri, 08 Mar 2019 15:10:39 GMT, David Eckardt wrote:

Hello,

I am writing a network client program using Vibe on Linux. The program needs to log out from the server on shutdown, when SIGINT is raised (like pressing Ctrl+C). In order to do so, I tried to use createFileDescriptorEvent with a Linux signalfd.
What happens is that FileDescriptorEvent.wait() returns immediately rather than waiting for the signalfd to be ready for reading. I traced it down to a recv(MSG_PEEK) call in eventcore. This call fails with ENOTSOCK.
My question now is, in order to understand how non-socket file descriptors could be supported: What is the purpose of this call?

Thanks,
David

createFileDescriptorEvent is currently limited to sockets or socket-like file descriptors by just blindly using eventcore's socket mechanism. This could be extended in the future, but for the time being I'd recommend to instead drop one level deeper and use eventcore directly, which already wraps signalfd.

To ensure proper interaction with the task switching logic, a yieldLock should be used in conjunction with starting a task within the callback (except if the shutdown procedure does not require I/O or yielding execution for another reason):

/+ dub.sdl:
	dependency "vibe-core" version="~>1.6"
+/
import vibe.core.core;
import vibe.core.log;

import core.time;
import core.sys.posix.signal;
import eventcore.core;

void main()
{
	bool do_shutdown;

	auto t = runTask({
		while (!do_shutdown) {
			sleep(500.msecs);
			logInfo("Still alive...");
		}
		sleep(500.msecs);
	});

	eventDriver.signals.listen(SIGINT, (id, status, sig) {
		auto l = yieldLock();

		eventDriver.signals.releaseRef(id);
	
		logInfo("Caught SIGINT!");

		runTask({
			logInfo("Initiating shutdown procedure...");
			do_shutdown = true;
			t.join();
			logInfo("Shut down!");
			exitEventLoop();
		});
	});

	runApplication();
}

At some point the signalfd functionality should get integrated properly into vibe-core, but until then this should work.

Re: Custom shutdown on SIGINT

This works very well, thank you!

Re: Custom shutdown on SIGINT

Following up on my other post, my main task reads from a TCP socket and needs to be interrupted on a signal:

void main()
{
	bool do_shutdown;

	auto t = runTask({
		auto conn = connectTCP("localhost", 4711);
		auto recvbuf = new ubyte[12345];
		while (!do_shutdown) {
			conn.read(recvbuf);
			logInfo("Still alive...");
		}
	});

	eventDriver.signals.listen(SIGINT, (id, status, sig) {
		auto l = yieldLock();

		eventDriver.signals.releaseRef(id);
	
		logInfo("Caught SIGINT!");

		runTask({
			logInfo("Initiating shutdown procedure...");
			// Cancel the read() call in the other task
			t.join();
			logInfo("Shut down!");
			exitEventLoop();
		});
	});

	runApplication();
}

How do I cancel the read call during a shutdown? I found eventcore.socket.cancelRead, which accepts a ref StreamSocket argument, and I am not sure how to pass a TCPSocket.

Re: Custom shutdown on SIGINT

On Mon, 11 Mar 2019 14:25:10 GMT, David Eckardt wrote:

Following up on my other post, my main task reads from a TCP socket and needs to be interrupted on a signal:

void main()
{
	bool do_shutdown;

	auto t = runTask({
		auto conn = connectTCP("localhost", 4711);
		auto recvbuf = new ubyte[12345];
		while (!do_shutdown) {
			conn.read(recvbuf);
			logInfo("Still alive...");
		}
	});

	eventDriver.signals.listen(SIGINT, (id, status, sig) {
		auto l = yieldLock();

		eventDriver.signals.releaseRef(id);
	
		logInfo("Caught SIGINT!");

		runTask({
			logInfo("Initiating shutdown procedure...");
			// Cancel the read() call in the other task
			t.join();
			logInfo("Shut down!");
			exitEventLoop();
		});
	});

	runApplication();
}

How do I cancel the read call during a shutdown? I found eventcore.socket.cancelRead, which accepts a ref StreamSocket argument, and I am not sure how to pass a TCPSocket.

You can do a t.interrupt() call, which causes an InterruptException to be thrown from read (which internally calls cancelRead).

BTW, there should also be a try-catch around the whole task function, which catches this exception, as well as others that may be thrown by connectTCP or read, but currently uncaught exceptions will just terminate the task, so in this case that may be okay.

Re: Custom shutdown on SIGINT

You can do a t.interrupt() call, which causes an InterruptException to be thrown from read (which internally calls cancelRead).

Thank you, I’ll try that.

BTW, there should also be a try-catch around the whole task function, which catches this exception, as well as others that may be thrown by connectTCP or read, but currently uncaught exceptions will just terminate the task, so in this case that may be okay.

I see.

Re: Custom shutdown on SIGINT

Here is a more complete example of what I am doing.

import vibe.core.core;
import vibe.core.log;
import vibe.core.net;
import vibe.stream.tls;
import vibe.stream.operations;
import vibe.stream.wrapper;
import eventcore.core;
import core.time: seconds;
import core.stdc.signal;

void main()
{
	ConnectionStream conn;

	auto listener = runTask({
		const server_cert_path = "";
		const client_cert_path = "";
		const private_key_path = "";
		auto sslctx = createTLSContext(TLSContextKind.client);
		sslctx.useTrustedCertificateFile(server_cert_path);
		sslctx.useCertificateChainFile(client_cert_path);
		sslctx.usePrivateKeyFile(private_key_path);

		auto tcp_connection = connectTCP("example.org", 4711);
		conn = createConnectionProxyStream(
			createTLSStream(tcp_connection, sslctx, "example.org"),
			tcp_connection
		);

		auto heartbeat = runTask({
			setTimer(
				seconds(60),
				{conn.write("still alive");}, // succeeds
				true);
		});

		auto recvbuf = new ubyte[12345];
		while (true) {
			conn.read(recvbuf);
			logInfo("Still alive...");
		}
	});

	eventDriver.signals.listen(SIGINT, (id, status, sig) {
		auto l = yieldLock();
		eventDriver.signals.releaseRef(id);
		auto shutdown = runTask({
			conn.write("shutting down");
			// AssertError@eventcore/drivers/posix/driver.d(340):
			// Overwriting notification callback.
		});
	});

	runApplication();
}

Both the heartbeat and the shutdown task write to the socket (more precisely, the TLS output stream) while read is blocked in the listener task. heartbeat succeeds, shutdown fails. Any idea why that might be?

Now I use listener.interrupt() to interrupt the listener Task.

import vibe.core.core;
import vibe.core.log;
import vibe.core.net;
import vibe.stream.tls;
import vibe.stream.operations;
import vibe.stream.wrapper;
import eventcore.core;
import core.time: seconds;
import core.stdc.signal;

void main()
{
	auto listener = runTask({
		const server_cert_path = "";
		const client_cert_path = "";
		const private_key_path = "";
		auto sslctx = createTLSContext(TLSContextKind.client);
		sslctx.useTrustedCertificateFile(server_cert_path);
		sslctx.useCertificateChainFile(client_cert_path);
		sslctx.usePrivateKeyFile(private_key_path);

		auto tcp_connection = connectTCP("example.org", 4711);
		auto conn = createConnectionProxyStream(
			createTLSStream(tcp_connection, sslctx, "example.org"),
			tcp_connection
		);

		auto heartbeat = runTask({
			setTimer(
				seconds(60),
				{conn.write("still alive");},
				true);
		});

		auto recvbuf = new ubyte[12345];
		while (true) {
			// This would be wrapped in a try/catch to call
			// conn.write("shutting down") and break the loop if interrupted.
			conn.read(recvbuf);
			logInfo("Still alive...");
		}
	});

	eventDriver.signals.listen(SIGINT, (id, status, sig) {
		auto l = yieldLock();
		eventDriver.signals.releaseRef(id);
		auto shutdown = runTask({
			listener.interrupt();
			listener.join();
		});
	});

	runApplication();
}

I would expect the read call to throw InterruptException when interrupted. Instead it throws object.Exception@../../.dub/packages/vibe-d-0.8.4/vibe-d/tls/vibe/stream/openssl.d(381): Reading from TLS stream: error:80000001:lib(128):func(0):reason(1) (2147483649). Do you know what I am doing wrong?

Re: Custom shutdown on SIGINT

I now investigated a little more what happens when I interrupt the listening task, as you suggested:

You can do a t.interrupt() call, which causes an InterruptException to be thrown from read (which internally calls cancelRead).

Like I said before, I am using a TLS connection. An exception is thrown; however, it is not an InterruptException but a generic Exception. The message is

Reading from TLS stream: error:80000001:lib(128):func(0):reason(1) (2147483649)

With setLogLevel(LogLevel.debug_) I see a debug message

OpenSSL error at ../../.dub/packages/vibe-d-0.8.4/vibe-d/tls/vibe/stream/openssl.d:1149: error:80000001:lib(128):func(0):reason(1) (Error reading from underlying stream: Task interrupted.)

So this seems to be the reaction to the task interrupt. The problem now is that with a generic Exception the function calling read cannot distinguish the interrupt from other causes of failure.

I would be happy to provide a patch fixing this but I don’t really know where to look. Would you have any advice? Thanks in advance!