mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
519 lines
16 KiB
C
519 lines
16 KiB
C
#ifndef _DARWIN_C_SOURCE
|
|
#define _DARWIN_C_SOURCE 1
|
|
#endif
|
|
#ifndef _POSIX_C_SOURCE
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#endif
|
|
#include "scr_mailbox.h"
|
|
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <stdatomic.h>
|
|
#include <stdlib.h>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
struct ScrMailbox {
|
|
atomic_size_t references;
|
|
ScrMailEvent *head;
|
|
ScrMailEvent **tail;
|
|
bool closed;
|
|
#ifdef _WIN32
|
|
SRWLOCK lock;
|
|
CONDITION_VARIABLE condition;
|
|
#else
|
|
pthread_mutex_t lock;
|
|
pthread_cond_t condition;
|
|
int read_fd;
|
|
int write_fd;
|
|
#endif
|
|
};
|
|
|
|
static void scr_mailbox_lock(ScrMailbox *mailbox) {
|
|
#ifdef _WIN32
|
|
AcquireSRWLockExclusive(&mailbox->lock);
|
|
#else
|
|
(void)pthread_mutex_lock(&mailbox->lock);
|
|
#endif
|
|
}
|
|
|
|
static void scr_mailbox_unlock(ScrMailbox *mailbox) {
|
|
#ifdef _WIN32
|
|
ReleaseSRWLockExclusive(&mailbox->lock);
|
|
#else
|
|
(void)pthread_mutex_unlock(&mailbox->lock);
|
|
#endif
|
|
}
|
|
|
|
static void scr_mailbox_signal(ScrMailbox *mailbox) {
|
|
#ifdef _WIN32
|
|
WakeAllConditionVariable(&mailbox->condition);
|
|
#else
|
|
unsigned char byte = 1;
|
|
/* Nonblocking wakeups coalesce when the pipe is full. The protected
|
|
* queue is authoritative, so a full pipe cannot lose a message. */
|
|
ssize_t written;
|
|
do { written = write(mailbox->write_fd, &byte, 1); } while (written < 0 && errno == EINTR);
|
|
(void)pthread_cond_broadcast(&mailbox->condition);
|
|
#endif
|
|
}
|
|
|
|
static void scr_mailbox_drain(ScrMailbox *mailbox) {
|
|
#ifndef _WIN32
|
|
unsigned char bytes[128];
|
|
ssize_t count;
|
|
do { count = read(mailbox->read_fd, bytes, sizeof bytes); }
|
|
while (count > 0 || (count < 0 && errno == EINTR));
|
|
#else
|
|
(void)mailbox;
|
|
#endif
|
|
}
|
|
|
|
ScrMailbox *scr_mailbox_new(void) {
|
|
ScrMailbox *mailbox = calloc(1, sizeof(*mailbox));
|
|
if (!mailbox) scr_trap("scriptc: out of memory\n");
|
|
atomic_init(&mailbox->references, 1);
|
|
mailbox->tail = &mailbox->head;
|
|
#ifdef _WIN32
|
|
InitializeSRWLock(&mailbox->lock);
|
|
InitializeConditionVariable(&mailbox->condition);
|
|
#else
|
|
int pipe_fd[2];
|
|
if (pipe(pipe_fd) != 0) goto failed;
|
|
mailbox->read_fd = pipe_fd[0];
|
|
mailbox->write_fd = pipe_fd[1];
|
|
if (fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK) < 0 ||
|
|
fcntl(pipe_fd[1], F_SETFL, O_NONBLOCK) < 0 ||
|
|
fcntl(pipe_fd[0], F_SETFD, FD_CLOEXEC) < 0 ||
|
|
fcntl(pipe_fd[1], F_SETFD, FD_CLOEXEC) < 0) goto failed_pipe;
|
|
if (pthread_mutex_init(&mailbox->lock, NULL) != 0) goto failed_pipe;
|
|
pthread_condattr_t attributes;
|
|
if (pthread_condattr_init(&attributes) != 0) goto failed_lock;
|
|
#if !defined(__APPLE__)
|
|
if (pthread_condattr_setclock(&attributes, CLOCK_MONOTONIC) != 0) {
|
|
pthread_condattr_destroy(&attributes);
|
|
goto failed_lock;
|
|
}
|
|
#endif
|
|
int error = pthread_cond_init(&mailbox->condition, &attributes);
|
|
pthread_condattr_destroy(&attributes);
|
|
if (error != 0) goto failed_lock;
|
|
#endif
|
|
return mailbox;
|
|
#ifndef _WIN32
|
|
failed_lock:
|
|
pthread_mutex_destroy(&mailbox->lock);
|
|
failed_pipe:
|
|
close(pipe_fd[0]);
|
|
close(pipe_fd[1]);
|
|
failed:
|
|
free(mailbox);
|
|
static const char message[] = "Could not create worker message channel";
|
|
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "ERR_WORKER_INIT_FAILED");
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
ScrMailbox *scr_mailbox_retain(ScrMailbox *mailbox) {
|
|
atomic_fetch_add_explicit(&mailbox->references, 1, memory_order_relaxed);
|
|
return mailbox;
|
|
}
|
|
|
|
void scr_mail_event_free(ScrMailEvent *event) {
|
|
if (!event) return;
|
|
scr_message_free(event->message);
|
|
free(event->data);
|
|
free(event);
|
|
}
|
|
|
|
void scr_mailbox_release(ScrMailbox *mailbox) {
|
|
if (!mailbox || atomic_fetch_sub_explicit(&mailbox->references, 1, memory_order_acq_rel) != 1) return;
|
|
while (mailbox->head) {
|
|
ScrMailEvent *event = mailbox->head;
|
|
mailbox->head = event->next;
|
|
scr_mail_event_free(event);
|
|
}
|
|
#ifndef _WIN32
|
|
pthread_cond_destroy(&mailbox->condition);
|
|
pthread_mutex_destroy(&mailbox->lock);
|
|
close(mailbox->read_fd);
|
|
close(mailbox->write_fd);
|
|
#endif
|
|
free(mailbox);
|
|
}
|
|
|
|
bool scr_mailbox_post(ScrMailbox *mailbox, ScrMailEvent *event) {
|
|
scr_mailbox_lock(mailbox);
|
|
if (mailbox->closed) {
|
|
scr_mailbox_unlock(mailbox);
|
|
scr_mail_event_free(event);
|
|
return false;
|
|
}
|
|
event->next = NULL;
|
|
*mailbox->tail = event;
|
|
mailbox->tail = &event->next;
|
|
scr_mailbox_signal(mailbox);
|
|
scr_mailbox_unlock(mailbox);
|
|
return true;
|
|
}
|
|
|
|
ScrMailEvent *scr_mailbox_take(ScrMailbox *mailbox) {
|
|
scr_mailbox_lock(mailbox);
|
|
ScrMailEvent *event = mailbox->head;
|
|
if (event) {
|
|
mailbox->head = event->next;
|
|
event->next = NULL;
|
|
}
|
|
if (!mailbox->head) {
|
|
mailbox->tail = &mailbox->head;
|
|
scr_mailbox_drain(mailbox);
|
|
}
|
|
scr_mailbox_unlock(mailbox);
|
|
return event;
|
|
}
|
|
|
|
ScrMailEvent *scr_mailbox_take_kind(ScrMailbox *mailbox, unsigned kind, uint64_t source) {
|
|
scr_mailbox_lock(mailbox);
|
|
ScrMailEvent **slot = &mailbox->head;
|
|
while (*slot && ((*slot)->kind != kind || (*slot)->source != source)) slot = &(*slot)->next;
|
|
ScrMailEvent *event = *slot;
|
|
if (event) {
|
|
*slot = event->next;
|
|
if (mailbox->tail == &event->next) mailbox->tail = slot;
|
|
event->next = NULL;
|
|
}
|
|
if (!mailbox->head) {
|
|
mailbox->tail = &mailbox->head;
|
|
scr_mailbox_drain(mailbox);
|
|
}
|
|
scr_mailbox_unlock(mailbox);
|
|
return event;
|
|
}
|
|
|
|
void scr_mailbox_close(ScrMailbox *mailbox) {
|
|
scr_mailbox_lock(mailbox);
|
|
mailbox->closed = true;
|
|
scr_mailbox_signal(mailbox);
|
|
scr_mailbox_unlock(mailbox);
|
|
}
|
|
|
|
bool scr_mailbox_pending(ScrMailbox *mailbox) {
|
|
scr_mailbox_lock(mailbox);
|
|
bool pending = mailbox->head != NULL;
|
|
scr_mailbox_unlock(mailbox);
|
|
return pending;
|
|
}
|
|
|
|
void scr_mailbox_wait(ScrMailbox *mailbox, double milliseconds) {
|
|
if (!(milliseconds > 0)) return;
|
|
scr_mailbox_lock(mailbox);
|
|
if (!mailbox->head && !mailbox->closed) {
|
|
#ifdef _WIN32
|
|
DWORD timeout = milliseconds >= INFINITE ? INFINITE : (DWORD)ceil(milliseconds);
|
|
(void)SleepConditionVariableSRW(&mailbox->condition, &mailbox->lock, timeout, 0);
|
|
#else
|
|
if (isinf(milliseconds)) (void)pthread_cond_wait(&mailbox->condition, &mailbox->lock);
|
|
else {
|
|
/* The event loop rechecks its monotonic deadlines after every wake,
|
|
* including spurious ones. Bound conversion before casting time_t. */
|
|
if (milliseconds > 86400000) milliseconds = 86400000;
|
|
struct timespec timeout = {(time_t)(milliseconds / 1000),
|
|
(long)ceil(fmod(milliseconds, 1000) * 1000000)};
|
|
if (timeout.tv_nsec == 1000000000) { timeout.tv_sec++; timeout.tv_nsec = 0; }
|
|
#ifdef __APPLE__
|
|
(void)pthread_cond_timedwait_relative_np(&mailbox->condition, &mailbox->lock, &timeout);
|
|
#else
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
timeout.tv_sec += now.tv_sec;
|
|
timeout.tv_nsec += now.tv_nsec;
|
|
if (timeout.tv_nsec >= 1000000000) { timeout.tv_sec++; timeout.tv_nsec -= 1000000000; }
|
|
(void)pthread_cond_timedwait(&mailbox->condition, &mailbox->lock, &timeout);
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
scr_mailbox_unlock(mailbox);
|
|
}
|
|
|
|
int scr_mailbox_pollfd(const ScrMailbox *mailbox) {
|
|
#ifdef _WIN32
|
|
(void)mailbox;
|
|
return -1;
|
|
#else
|
|
return mailbox->read_fd;
|
|
#endif
|
|
}
|
|
|
|
/* ── MessageChannel ──────────────────────────────────────────────────── */
|
|
|
|
typedef struct {
|
|
ScrMailEvent *head; /* incoming events for this end, in order */
|
|
ScrMailEvent **tail;
|
|
size_t count;
|
|
ScrMailbox *owner; /* the owning thread's inbox (+1), or NULL in transit */
|
|
bool closed; /* this end called close() */
|
|
bool peer_closed; /* the other end's close reached this end */
|
|
uint64_t peer_closed_seq; /* when: notices are delivered in this order */
|
|
} ScrChannelEnd;
|
|
|
|
static atomic_uint_fast64_t scr_channel_notice_seq;
|
|
|
|
struct ScrChannel {
|
|
atomic_size_t references;
|
|
ScrChannelEnd end[2];
|
|
#ifdef _WIN32
|
|
SRWLOCK lock;
|
|
#else
|
|
pthread_mutex_t lock;
|
|
#endif
|
|
};
|
|
|
|
static void scr_channel_lock(ScrChannel *channel) {
|
|
#ifdef _WIN32
|
|
AcquireSRWLockExclusive(&channel->lock);
|
|
#else
|
|
(void)pthread_mutex_lock(&channel->lock);
|
|
#endif
|
|
}
|
|
|
|
static void scr_channel_unlock(ScrChannel *channel) {
|
|
#ifdef _WIN32
|
|
ReleaseSRWLockExclusive(&channel->lock);
|
|
#else
|
|
(void)pthread_mutex_unlock(&channel->lock);
|
|
#endif
|
|
}
|
|
|
|
ScrChannel *scr_channel_new(void) {
|
|
ScrChannel *channel = calloc(1, sizeof(*channel));
|
|
if (!channel) scr_trap("scriptc: out of memory\n");
|
|
atomic_init(&channel->references, 1);
|
|
for (int side = 0; side < 2; side++) channel->end[side].tail = &channel->end[side].head;
|
|
#ifdef _WIN32
|
|
InitializeSRWLock(&channel->lock);
|
|
#else
|
|
if (pthread_mutex_init(&channel->lock, NULL) != 0) scr_trap("scriptc: out of memory\n");
|
|
#endif
|
|
return channel;
|
|
}
|
|
|
|
ScrChannel *scr_channel_retain(ScrChannel *channel) {
|
|
atomic_fetch_add_explicit(&channel->references, 1, memory_order_relaxed);
|
|
return channel;
|
|
}
|
|
|
|
void scr_channel_release(ScrChannel *channel) {
|
|
if (!channel || atomic_fetch_sub_explicit(&channel->references, 1, memory_order_acq_rel) != 1)
|
|
return;
|
|
for (int side = 0; side < 2; side++) {
|
|
ScrChannelEnd *end = &channel->end[side];
|
|
while (end->head) {
|
|
ScrMailEvent *event = end->head;
|
|
end->head = event->next;
|
|
scr_mail_event_free(event);
|
|
}
|
|
scr_mailbox_release(end->owner);
|
|
}
|
|
#ifndef _WIN32
|
|
pthread_mutex_destroy(&channel->lock);
|
|
#endif
|
|
free(channel);
|
|
}
|
|
|
|
/* Wakes the owner's loop with a wake_kind event. */
|
|
static void scr_channel_wake(ScrMailbox *owner, unsigned wake_kind) {
|
|
if (!owner) return;
|
|
ScrMailEvent *wake = calloc(1, sizeof(*wake));
|
|
if (!wake) scr_trap("scriptc: out of memory\n");
|
|
wake->kind = wake_kind;
|
|
scr_mailbox_post(owner, wake);
|
|
scr_mailbox_release(owner);
|
|
}
|
|
|
|
void scr_channel_set_owner(ScrChannel *channel, int side, ScrMailbox *owner, unsigned wake_kind) {
|
|
scr_channel_lock(channel);
|
|
ScrChannelEnd *end = &channel->end[side];
|
|
ScrMailbox *old = end->owner;
|
|
end->owner = owner ? scr_mailbox_retain(owner) : NULL;
|
|
ScrMailbox *wake = end->owner && (end->head || end->peer_closed) ? scr_mailbox_retain(end->owner) : NULL;
|
|
scr_channel_unlock(channel);
|
|
scr_mailbox_release(old);
|
|
scr_channel_wake(wake, wake_kind);
|
|
}
|
|
|
|
/* postMessage from `side`: the message (consumed) queues on the other end
|
|
* unless either end has closed. */
|
|
bool scr_channel_post(ScrChannel *channel, int side, ScrMessage *message, unsigned message_kind,
|
|
unsigned wake_kind) {
|
|
ScrMailEvent *event = calloc(1, sizeof(*event));
|
|
if (!event) scr_trap("scriptc: out of memory\n");
|
|
event->kind = message_kind;
|
|
event->message = message;
|
|
scr_channel_lock(channel);
|
|
ScrChannelEnd *peer = &channel->end[1 - side];
|
|
if (channel->end[side].closed || peer->closed) {
|
|
scr_channel_unlock(channel);
|
|
scr_mail_event_free(event);
|
|
return false;
|
|
}
|
|
*peer->tail = event;
|
|
peer->tail = &event->next;
|
|
peer->count++;
|
|
ScrMailbox *wake = peer->owner ? scr_mailbox_retain(peer->owner) : NULL;
|
|
scr_channel_unlock(channel);
|
|
scr_channel_wake(wake, wake_kind);
|
|
return true;
|
|
}
|
|
|
|
/* close() on `side`: nothing more is posted in either direction. */
|
|
void scr_channel_close(ScrChannel *channel, int side) {
|
|
scr_channel_lock(channel);
|
|
channel->end[side].closed = true;
|
|
scr_channel_unlock(channel);
|
|
}
|
|
|
|
/* The closed port's 'close' ran: the other end learns it, after its queued
|
|
* messages. */
|
|
void scr_channel_notify_close(ScrChannel *channel, int side, unsigned wake_kind) {
|
|
scr_channel_lock(channel);
|
|
ScrChannelEnd *peer = &channel->end[1 - side];
|
|
ScrMailbox *wake = NULL;
|
|
if (!peer->peer_closed && !peer->closed) {
|
|
peer->peer_closed = true;
|
|
peer->peer_closed_seq = atomic_fetch_add(&scr_channel_notice_seq, 1) + 1;
|
|
wake = peer->owner ? scr_mailbox_retain(peer->owner) : NULL;
|
|
}
|
|
scr_channel_unlock(channel);
|
|
scr_channel_wake(wake, wake_kind);
|
|
}
|
|
|
|
/* The next queued message for `side`, or NULL. */
|
|
ScrMailEvent *scr_channel_take(ScrChannel *channel, int side) {
|
|
scr_channel_lock(channel);
|
|
ScrChannelEnd *end = &channel->end[side];
|
|
ScrMailEvent *event = end->head;
|
|
if (event) {
|
|
end->head = event->next;
|
|
if (!end->head) end->tail = &end->head;
|
|
end->count--;
|
|
event->next = NULL;
|
|
}
|
|
scr_channel_unlock(channel);
|
|
return event;
|
|
}
|
|
|
|
size_t scr_channel_count(ScrChannel *channel, int side) {
|
|
scr_channel_lock(channel);
|
|
size_t count = channel->end[side].count;
|
|
scr_channel_unlock(channel);
|
|
return count;
|
|
}
|
|
|
|
/* The other end's close notice order (0: none). */
|
|
uint64_t scr_channel_peer_closed(ScrChannel *channel, int side) {
|
|
scr_channel_lock(channel);
|
|
uint64_t seq = channel->end[side].peer_closed ? channel->end[side].peer_closed_seq : 0;
|
|
scr_channel_unlock(channel);
|
|
return seq;
|
|
}
|
|
|
|
/* Drops the queued messages for `side` (its port closed). */
|
|
void scr_channel_drop(ScrChannel *channel, int side) {
|
|
scr_channel_lock(channel);
|
|
ScrChannelEnd *end = &channel->end[side];
|
|
ScrMailEvent *head = end->head;
|
|
end->head = NULL;
|
|
end->tail = &end->head;
|
|
end->count = 0;
|
|
scr_channel_unlock(channel);
|
|
while (head) {
|
|
ScrMailEvent *event = head;
|
|
head = event->next;
|
|
scr_mail_event_free(event);
|
|
}
|
|
}
|
|
|
|
/* ── BroadcastChannel registry ───────────────────────────────────────── */
|
|
|
|
/* Every open BroadcastChannel in the process, by name: each has a channel
|
|
* whose end 0 queues its incoming messages. */
|
|
typedef struct ScrBroadcastEntry {
|
|
char *name;
|
|
size_t length;
|
|
ScrChannel *channel; /* +1 */
|
|
struct ScrBroadcastEntry *next;
|
|
} ScrBroadcastEntry;
|
|
|
|
static ScrBroadcastEntry *scr_broadcast_entries;
|
|
#ifdef _WIN32
|
|
static SRWLOCK scr_broadcast_lock = SRWLOCK_INIT;
|
|
#define SCR_BROADCAST_LOCK() AcquireSRWLockExclusive(&scr_broadcast_lock)
|
|
#define SCR_BROADCAST_UNLOCK() ReleaseSRWLockExclusive(&scr_broadcast_lock)
|
|
#else
|
|
static pthread_mutex_t scr_broadcast_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
#define SCR_BROADCAST_LOCK() (void)pthread_mutex_lock(&scr_broadcast_lock)
|
|
#define SCR_BROADCAST_UNLOCK() (void)pthread_mutex_unlock(&scr_broadcast_lock)
|
|
#endif
|
|
|
|
void scr_broadcast_join(const char *name, size_t length, ScrChannel *channel) {
|
|
ScrBroadcastEntry *entry = calloc(1, sizeof(*entry));
|
|
if (!entry) scr_trap("scriptc: out of memory\n");
|
|
entry->name = malloc(length ? length : 1);
|
|
if (!entry->name) scr_trap("scriptc: out of memory\n");
|
|
memcpy(entry->name, name, length);
|
|
entry->length = length;
|
|
entry->channel = scr_channel_retain(channel);
|
|
SCR_BROADCAST_LOCK();
|
|
/* registration order is delivery order to the subscribers */
|
|
ScrBroadcastEntry **tail = &scr_broadcast_entries;
|
|
while (*tail) tail = &(*tail)->next;
|
|
*tail = entry;
|
|
SCR_BROADCAST_UNLOCK();
|
|
}
|
|
|
|
void scr_broadcast_leave(ScrChannel *channel) {
|
|
ScrBroadcastEntry *gone = NULL;
|
|
SCR_BROADCAST_LOCK();
|
|
for (ScrBroadcastEntry **at = &scr_broadcast_entries; *at; at = &(*at)->next) {
|
|
if ((*at)->channel != channel) continue;
|
|
gone = *at;
|
|
*at = gone->next;
|
|
break;
|
|
}
|
|
SCR_BROADCAST_UNLOCK();
|
|
if (!gone) return;
|
|
scr_channel_release(gone->channel);
|
|
free(gone->name);
|
|
free(gone);
|
|
}
|
|
|
|
/* The other open channels named `name` (+1 each, in registration order);
|
|
* the caller frees the array. */
|
|
size_t scr_broadcast_peers(const char *name, size_t length, ScrChannel *self, ScrChannel ***out) {
|
|
size_t n = 0, cap = 0;
|
|
ScrChannel **peers = NULL;
|
|
SCR_BROADCAST_LOCK();
|
|
for (ScrBroadcastEntry *entry = scr_broadcast_entries; entry; entry = entry->next) {
|
|
if (entry->channel == self || entry->length != length ||
|
|
(length && memcmp(entry->name, name, length) != 0))
|
|
continue;
|
|
if (n == cap) {
|
|
cap = cap ? cap * 2 : 4;
|
|
ScrChannel **grown = realloc(peers, cap * sizeof *grown);
|
|
if (!grown) scr_trap("scriptc: out of memory\n");
|
|
peers = grown;
|
|
}
|
|
peers[n++] = scr_channel_retain(entry->channel);
|
|
}
|
|
SCR_BROADCAST_UNLOCK();
|
|
*out = peers;
|
|
return n;
|
|
}
|