From 9e1c6fc49ce4127ff7aa8892a52d3ca8acdf1854 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Sun, 13 May 2018 23:42:23 -0700 Subject: libpulp: introduce mailbox concept The sleep functions, which are few, and the only current means for fibers to block/enter the scheduler, now take an optional pulp_mailbox_t * parameter. When supplied, other fibers may communicate with the sleeping fiber via pulp_fiber_get_mailslot(). This function checks the destination fiber for a mailbox and verifies there's space available. On success, it returns a pointer to the next available mailslot while advancing the mailboxes mail count. The caller may then use this mailslot pointer to write directly to the void * it references in the mailbox. If the receiving fiber took care to populate its mailbox with slots referencing external memory, the sender could dereference the mailslot's value to find the external memory and write larger messages without needing to allocate new space itself only for the reciever to have to free it shortly. It's also possible to not do anything at all with the mailslot. Simply successfully getting a mailslot communicates _something_ to the recipient by virtue of the count advancing. The returned mailslot can only be considered valid by the calling fiber until it sleeps, after that the slot must be treated as reclaimed from the perspective of the fiber that called pulp_fiber_get_mailslot(). When the sleeping fiber wakes and returns from its mailbox-enabled sleep call, it simply looks at the count member of the mailbox to see if any mail was received. It's up to the implementation what is done with the contents of the mailbox. The mailbox count is always reset to 0 automatically at the start of a mailbox-enabled sleep. --- src/pulp.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/pulp.h | 11 ++++++++-- 2 files changed, 73 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/pulp.c b/src/pulp.c index 4166dc5..140a5f4 100644 --- a/src/pulp.c +++ b/src/pulp.c @@ -57,6 +57,7 @@ typedef struct pulp_fiber_t { union { struct { pulp_usec_t alarm; + pulp_mailbox_t *mailbox; /* non-NULL if fiber receives mail */ } sleep; } state; @@ -409,10 +410,15 @@ pulp_fiber_t * pulp_self(pulp_t *pulp) /* sleep for the supplied number of microseconds (not public) */ -static void pulp_usleep(pulp_t *pulp, unsigned useconds) +/* if mailbox is non-NULL it may be used to receive mail while sleeping via pulp_fiber_get_mailslot() */ +static void pulp_usleep(pulp_t *pulp, unsigned useconds, pulp_mailbox_t *mailbox) { assert(pulp); + pulp->current->state.sleep.mailbox = mailbox; + if (mailbox) + mailbox->count = 0; + pulp->current->state.sleep.alarm = pulp->now + useconds; put_current_fiber(pulp, &pulp->fibers.sleep); pulp_schedule(pulp); @@ -420,20 +426,22 @@ static void pulp_usleep(pulp_t *pulp, unsigned useconds) /* sleep for the supplied number of milliseconds */ -void pulp_msleep(pulp_t *pulp, unsigned milliseconds) +/* if mailbox is non-NULL it may be used to receive mail while sleeping via pulp_fiber_get_mailslot() */ +void pulp_msleep(pulp_t *pulp, unsigned milliseconds, pulp_mailbox_t *mailbox) { assert(pulp); - return pulp_usleep(pulp, milliseconds * PULP_USECS_PER_MSEC); + return pulp_usleep(pulp, milliseconds * PULP_USECS_PER_MSEC, mailbox); } /* sleep for the supplied number of seconds */ -void pulp_sleep(pulp_t *pulp, unsigned seconds) +/* if mailbox is non-NULL it may be used to receive mail while sleeping via pulp_fiber_get_mailslot() */ +void pulp_sleep(pulp_t *pulp, unsigned seconds, pulp_mailbox_t *mailbox) { assert(pulp); - return pulp_usleep(pulp, seconds * PULP_USECS_PER_SEC); + return pulp_usleep(pulp, seconds * PULP_USECS_PER_SEC, mailbox); } @@ -447,6 +455,57 @@ pulp_usec_t pulp_now(pulp_t *pulp) } +/* Get a mailslot in a destination fiber's mailbox. + * If the fiber has no mailbox, -ENOENT is returned. + * If the mailbox is full, -ENOSPC is returned. + * On success 0 is returned and the mailslot pointer is stored @ *res_mailslot. + * + * The mailslot can only be treated as valid until the calling fiber sleeps, + * after sleeping the receiving fiber may execute, then free, or clear and + * reuse its mailbox. + * + * This interface returns a pointer to the slot rather than accepting something + * like a void * value to store at the slot to allow a greater variety of data + * passing models. If your fibers are creating mailboxes having slots filled + * with pointers to valid memory, then the sender may dereference the + * mailslot's pointer to the space for storing the message rather than having + * to allocate space for passing messages larger than a pointer. + * + * Of course, if your scenario is simple enough, you can always simply write to + * the mailslot's void * worth of space. There's also the possibility of not + * writing anything at all; if the only thing needed is a basic signal - the + * mailbox count will be advanced wether you do anything with the mailslot or + * not. It's up to you to define the contract for your fibers to agree on. + */ +int pulp_fiber_get_mailslot(pulp_t *pulp, pulp_fiber_t *fiber, void ***res_mailslot) +{ + assert(pulp); + assert(fiber); + assert(res_mailslot); + + /* XXX: this doesn't currently implement any blocking - if the mailbox + * is full or the fiber isn't receiving mail (no mailbox), we simply return + * failure. It seems trivial to make this fiber go to sleep when the + * mailbox is full then retrying when it comes back though. We'll see if + * that's desirable (reliable delivery). What I'm expecting is that mailboxes + * should just be sized to never experience blocking. + */ + + /* TODO: nothing is sanity checked at this time, I assume the supplied fiber + * is valid and sleeping. + */ + if (!fiber->state.sleep.mailbox) + return -ENOENT; + + if (fiber->state.sleep.mailbox->count >= fiber->state.sleep.mailbox->size) + return -ENOSPC; + + *res_mailslot = &fiber->state.sleep.mailbox->slots[fiber->state.sleep.mailbox->count++]; + + return 0; +} + + /* TODO: interfaces for idling/waking fibers */ /* TODO: interfaces for synchronization across fibers */ /* all these are left to be implemented as their needs arise */ diff --git a/src/pulp.h b/src/pulp.h index 6111c80..c7edd0f 100644 --- a/src/pulp.h +++ b/src/pulp.h @@ -24,14 +24,21 @@ typedef struct pulp_t pulp_t; typedef uint64_t pulp_usec_t; typedef struct thunk_t thunk_t; +/* for conveniences like trivial stack allocation, this is public */ +typedef struct pulp_mailbox_t { + unsigned size, count; + void *slots[]; +} pulp_mailbox_t; + pulp_t * pulp_new(void); void pulp_free(pulp_t *pulp); int pulp_tick(pulp_t *pulp, unsigned *next_tick_delay_us); void pulp_run(pulp_t *pulp); pulp_fiber_t * pulp_fiber_new(pulp_t *pulp, unsigned delay_ms, thunk_t *thunk); -void pulp_msleep(pulp_t *pulp, unsigned milliseconds); -void pulp_sleep(pulp_t *pulp, unsigned seconds); +void pulp_msleep(pulp_t *pulp, unsigned milliseconds, pulp_mailbox_t *mailbox); +void pulp_sleep(pulp_t *pulp, unsigned seconds, pulp_mailbox_t *mailbox); pulp_usec_t pulp_now(pulp_t *pulp); pulp_fiber_t * pulp_self(pulp_t *pulp); +int pulp_fiber_get_mailslot(pulp_t *pulp, pulp_fiber_t *fiber, void ***res_mailslot); #endif -- cgit v1.2.3