# Copyright (c) 2023 Yuki Kimoto
# MIT License
class Go::OS::Signal {
version_from Go;
allow Go::Schedule;
use Go;
use Go::Channel;
use Sys::Signal;
use Sys::IO::Constant as IO;
use Sys;
use Errno;
use Thread;
use Go::Sync::WaitGroup;
use Hash;
use Sys::Signal::Handler;
use Sync::Mutex;
use Sys::Process;
# Class Variables
our $SIGNAL_READ_FD : int;
INIT {
$SIGNAL_READ_FD = -1;
}
our $SIGNAL_WRITE_FD : int;
INIT {
$SIGNAL_WRITE_FD = -1;
}
our $NOTIFIED_SIGNALS_H : cache Hash of Go::Channel[];
our $SIGNAL_HANDLER_WG : cache Go::Sync::WaitGroup;
static method ignore : void ($signal : int) {
Sys::Signal->signal($signal, Sys::Signal->SIG_GO);
$NOTIFIED_SIGNALS_H->{"$signal"} = new Go::Channel[0];
}
static method notify : void ($channel : Go::Channel, $signal : int) {
Sys::Signal->signal($signal, Sys::Signal->SIG_GO);
unless ($channel) {
die "\$channel must be defined.";
}
unless ($NOTIFIED_SIGNALS_H->{"$signal"}) {
$NOTIFIED_SIGNALS_H->{"$signal"} = new Go::Channel[0];
}
List->new_ref($NOTIFIED_SIGNALS_H->{"$signal"})->push($channel);
}
static method stop : void ($channel : Go::Channel, $signal : int) {
Sys::Signal->signal($signal, Sys::Signal->SIG_GO);
my $channels = $NOTIFIED_SIGNALS_H->{"$signal"} // new Go::Channel[0];
my $new_channels = (List of Go::Channel)List->new(new Go::Channel[0]);
for my $_ (@$channels) {
unless ($_ == $channel) {
$new_channels->push($_);
}
}
$NOTIFIED_SIGNALS_H->{"$signal"} = $new_channels->to_array;
}
static method start_signal_handler : void () {
if (&is_signal_handler_running) {
return;
}
my $signal_read_fd = -1;
my $signal_write_fd = -1;
Sys->pipe(\$signal_read_fd, \$signal_write_fd);
$SIGNAL_READ_FD = $signal_read_fd;
$SIGNAL_WRITE_FD = $signal_write_fd;
Sys::Signal->SET_SIG_GO_WRITE_FD($signal_write_fd);
$NOTIFIED_SIGNALS_H = Hash->new;
$SIGNAL_HANDLER_WG = Go::Sync::WaitGroup->new;
$SIGNAL_HANDLER_WG->add(1);
Go->go(method : void () {
Fn->defer(method : void () {
$SIGNAL_HANDLER_WG->done;
});
while (1) {
unless ($SIGNAL_READ_FD > -1) {
return;
}
my $buffer = (mutable string)new_string_len 4;
my $read_error_ref = [0];
my $thred_wg = Go::Sync::WaitGroup->new;
$thred_wg->add(1);
my $thread = Thread->new([$thred_wg : Go::Sync::WaitGroup, $buffer : mutable string, $read_error_ref : int[]] method : void () {
Fn->defer([$thred_wg : Go::Sync::WaitGroup] method : void () {
$thred_wg->done;
});
eval { Sys::IO->read($SIGNAL_READ_FD, $buffer, 4); }
if ($@) {
$read_error_ref->[0] = 1;
}
});
$thred_wg->wait;
$thread->join;
if ($read_error_ref->[0]) {
next;
}
my $numbers = new int [1];
Fn->memcpy($numbers, 0, $buffer, 0, 4);
my $got_signal = $numbers->[0];
if ($NOTIFIED_SIGNALS_H && (my $channels = $NOTIFIED_SIGNALS_H->{"$got_signal"})) {
for my $channel (@$channels) {
my $select = Go->new_select;
$select->set_non_blocking(1);
$select->add_write($channel, $got_signal);
$select->select;
}
}
Go->sleep_sec(0.01);
}
});
}
static method stop_signal_handler : void () {
unless (&is_signal_handler_running) {
return;
}
if ($NOTIFIED_SIGNALS_H) {
for my $signal (@{$NOTIFIED_SIGNALS_H->keys}) {
Sys::Signal->signal((int)$signal, Sys::Signal->SIG_DFL);
}
}
$NOTIFIED_SIGNALS_H = undef;
my $signal_write_fd = $SIGNAL_WRITE_FD;
$SIGNAL_WRITE_FD = -1;
my $signal_read_fd = $SIGNAL_READ_FD;
$SIGNAL_READ_FD = -1;
Sys::Signal->SET_SIG_GO_WRITE_FD(-1);
Sys::IO->close($signal_write_fd);
Sys::IO->close($signal_read_fd);
$SIGNAL_HANDLER_WG->wait;
$SIGNAL_HANDLER_WG = undef;
}
static method is_signal_handler_running : int () {
return $SIGNAL_READ_FD > -1;
}
}