Skip to content
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

core: refactor signal handling #480

Merged
merged 3 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions docs/types/txikijs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,35 +68,34 @@ declare global {
| 'SIGPOLL' | 'SIGPWR' | 'SIGSYS';

/**
* Signal handler function.
* Signal listener function.
*/
type SignalHandlerFunction = () => void;

interface SignalHandler {
/**
* The signal that this signal handler was registered for.
*/
signal: Signal;

/**
* Stop the signal handler. The registered signal handler function
* will no longer be called.
*/
close(): void;
}

type SignalListener = () => void;

/**
* Registers a handler for the given signal.
* Registers a listener for the given signal.
*
* ```js
* const h = tjs.signal('SIGINT', handleSigint);
* tjs.addSignalListener('SIGINT', handleSigint);
* ```
*
* @param sig Which signal to register a handler for.
* @param handler Handler function.
* @param sig Which signal to register a listener for.
* @param listener Listener function.
*/
function signal(sig: Signal, handler: SignalHandlerFunction): SignalHandler;

function addSignalListener(sig: Signal, listener: SignalListener): void;

/**
* Un-registers a listener for the given signal.
*
* ```js
* tjs.removeSignalListener('SIGINT', handleSigint);
* ```
*
* @param sig Which signal to un-register a listener for.
* @param listener Listener function.
*/
function removeSignalListener(sig: Signal, listener: SignalListener): void;

/**
* Send a signal to a process.
*
Expand Down
5,653 changes: 2,886 additions & 2,767 deletions src/bundles/c/core/core.c

Large diffs are not rendered by default.

8,902 changes: 4,450 additions & 4,452 deletions src/bundles/c/core/run-main.c

Large diffs are not rendered by default.

22 changes: 15 additions & 7 deletions src/js/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { alert, confirm, prompt } from './alert-confirm-prompt.js';
import { open, mkdir, mkstemp, rm } from './fs.js';
import pathModule from './path.js';
import { PosixSocket } from './posix-socket.js';
import { signal } from './signal.js';
import { addSignalListener, removeSignalListener } from './signal.js';
import { connect, listen } from './sockets.js';
import { createStdin, createStdout, createStderr } from './stdio.js';
import { bootstrapWorker } from './worker-bootstrap.js';
Expand Down Expand Up @@ -140,12 +140,20 @@ Object.defineProperty(tjs, 'rm', {
});

// Signals.
Object.defineProperty(tjs, 'signal', {
enumerable: true,
configurable: false,
writable: false,
value: signal
});
if (!core.isWorker) {
Object.defineProperty(tjs, 'addSignalListener', {
enumerable: true,
configurable: false,
writable: false,
value: addSignalListener
});
Object.defineProperty(tjs, 'removeSignalListener', {
enumerable: true,
configurable: false,
writable: false,
value: removeSignalListener
});
}

// Sockets.
Object.defineProperty(tjs, 'connect', {
Expand Down
38 changes: 36 additions & 2 deletions src/js/core/signal.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
const core = globalThis.__bootstrap;

export function signal(sig, handler) {
const data = Object.create(null);


function getSigData(signum) {
return data[signum] ?? (data[signum] = { sh: undefined, listeners: new Set() });
}

function getSigNum(sig) {
const signum = core.signals[sig];

if (typeof signum === 'undefined') {
throw new Error(`invalid signal: ${sig}`);
}

return core.signal(signum, handler);
return signum;
}

export function addSignalListener(sig, listener) {
const signum = getSigNum(sig);
const sd = getSigData(signum);

sd.listeners.add(listener);

if (!sd.sh) {
sd.sh = core.signal(signum, () => {
for (const listener of sd.listeners) {
listener();
}
});
}
}

export function removeSignalListener(sig, listener) {
const signum = getSigNum(sig);
const sd = getSigData(signum);

sd.listeners.delete(listener);

if (sd.listeners.size === 0 && sd.sh) {
sd.sh.close();
sd.sh = undefined;
}
}
4 changes: 1 addition & 3 deletions src/js/run-main/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ function _run(g) {
/* current X position of the cursor in the terminal */
var term_cursor_x = 0;

var sigint_h;

var { evalScript } = tjs[Symbol.for('tjs.internal')].core;

var encoder = new TextEncoder();
Expand All @@ -120,7 +118,7 @@ function _run(g) {
tjs.stdin.setRawMode(true);

/* install a Ctrl-C signal handler */
sigint_h = tjs.signal('SIGINT', sigint_handler);
tjs.addSignalListener('SIGINT', sigint_handler);

/* handler to read stdin */
term_read_handler();
Expand Down
10 changes: 8 additions & 2 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "private.h"
#include "tjs.h"

#include <signal.h>
#include <string.h>

#ifdef TJS__HAS_MIMALLOC
Expand Down Expand Up @@ -316,8 +317,9 @@ TJSRuntime *TJS_NewRuntimeInternal(bool is_worker, TJSRunOptions *options) {
JSValue global_obj = JS_GetGlobalObject(qrt->ctx);
JSAtom bootstrap_ns_atom = JS_NewAtom(qrt->ctx, "__bootstrap");
JSValue bootstrap_ns = JS_NewObjectProto(qrt->ctx, JS_NULL);
JS_DupValue(qrt->ctx, bootstrap_ns); // JS_SetProperty frees the value.
JS_SetProperty(qrt->ctx, global_obj, bootstrap_ns_atom, bootstrap_ns);

CHECK_EQ(JS_SetProperty(qrt->ctx, global_obj, bootstrap_ns_atom, JS_DupValue(qrt->ctx, bootstrap_ns)), 1);
CHECK_EQ(JS_SetPropertyStr(qrt->ctx, bootstrap_ns, "isWorker", JS_NewBool(qrt->ctx, is_worker)), 1);

tjs__bootstrap_core(qrt->ctx, bootstrap_ns);

Expand Down Expand Up @@ -399,6 +401,10 @@ void TJS_Initialize(int argc, char **argv) {

tjs__argc = argc;
tjs__argv = uv_setup_args(argc, argv);

#ifdef SIGPIPE
signal(SIGPIPE, SIG_IGN);
#endif
}

JSContext *TJS_GetJSContext(TJSRuntime *qrt) {
Expand Down
3 changes: 3 additions & 0 deletions tests/helpers/worker.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
if (self.addSignalListener || self.removeSignalListener) {
throw new Error('There are signals in a Worker!');
}

self.postMessage({foo: 42, bar: 'baz!'});
16 changes: 7 additions & 9 deletions tests/test-signal.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import assert from 'tjs:assert';

if (tjs.platform === 'windows') {
/* Signals emulated on Windows do not allow this to be tested
* by sending a signal via kill(), so don't continue the test.
*/
tjs.exit(0);
}

function sleep(seconds) {
return new Promise(resolve => setTimeout(resolve, seconds*1000));
}

const h = tjs.signal('SIGINT', () => {
tjs.addSignalListener('SIGINT', () => {
tjs.exit(0);
});
assert.eq(h.signal, 'SIGINT');

if (tjs.platform === 'windows') {
/* Signals emulated on Windows do not allow signal() to be tested
* by sending a signal via kill(), so don't continue the test.
*/
tjs.exit(0);
}

tjs.kill(tjs.pid, 'SIGINT');
await sleep(1);
Expand Down
Loading