diff options
| -rw-r--r-- | serve/serve.c | 390 |
1 files changed, 269 insertions, 121 deletions
diff --git a/serve/serve.c b/serve/serve.c index 65a5c0b..3b713ef 100644 --- a/serve/serve.c +++ b/serve/serve.c @@ -76,7 +76,17 @@ typedef enum { ROUTE_NONE, ROUTE_OBJECT, ROUTE_VIEW, ROUTE_INPUT, ROUTE_QUERY } /* A view's (or a query's) arguments come as the request's query: argued * says so, and given is its text, kept on the request's work slot. */ typedef struct App App; -typedef struct { RouteKind kind; uint32_t target; size_t from, count; bool argued; const char *given; App *app; } Route; +/* And the module it was resolved with: not applied under another (its app + * changed module meanwhile, docs/design/017's stage 3). */ +typedef struct { + RouteKind kind; + uint32_t target; + size_t from, count; + bool argued; + const char *given; + App *app; + const PureModule *module; +} Route; /* This node's key, for deliveries sealed to it (--key), and whether it * takes only those (--sealed-only), or only those from the senders named, @@ -118,6 +128,7 @@ typedef struct Connection { char key[65]; /* the document's key, or the view's or input's name */ RouteKind route; App *app; /* the app the path names (NULL: none of the host's) */ + const PureModule *module; /* the module its request was resolved with */ /* An app input's message: its fixed fields from the query, which fields * were given there, and whether the body is a JSON object of the rest. * Its ingress is [u32le payload size][payload]; with JSON it is [u32le @@ -175,6 +186,14 @@ typedef struct { bool gap; /* and whether they stopped at a gap */ } Applied; +/* The last save's snapshot an owner published, the connection threads' + * to show views from (NULL: none, or its app does not run). The owner's, + * so it outlasts the executors its app runs on. */ +typedef struct Published { + pthread_mutex_t lock; + PureSnapshot *snapshot; +} Published; + /* The app and its store. A connection thread runs it itself (an app of * documents only), or one owner thread runs it for all connection threads. * The owner publishes a snapshot of the states after each save, and the @@ -195,11 +214,9 @@ typedef struct { uint64_t ticks; uint64_t memory_refused; /* events refused for the app's memory budget */ bool answers_wait; /* an answer the app had no room for: tried again after ANSWER_WAIT_MS */ - /* The owner's: the last save's snapshot (NULL: the owner shows views), - * taken under the lock, and the app's changes it shows. */ - bool publishes; - pthread_mutex_t published_lock; - PureSnapshot *published; + /* The owner's: where its snapshots go (NULL: it shows its views + * itself), and the app's changes the last one shows. */ + Published *published; uint64_t published_changes, publications; struct PureSurfaceHost *surface; /* told of each snapshot published */ /* The owner's: outputs wired to HTTP receivers, and how far each was trimmed. */ @@ -322,6 +339,11 @@ typedef struct Owner { Executor executor; pthread_mutex_t lock; pthread_cond_t waiting; + bool made; /* its locks, made once (its app may start again) */ + Published published; + /* Under lock: the module its executor runs (NULL: none). A request + * resolved with another is not taken (503 stopped). */ + const PureModule *module; Job *jobs; size_t count, capacity; uint64_t arrivals; /* requests handed to the owner, ever */ @@ -333,6 +355,7 @@ typedef struct Owner { * with work to do, or running on one; again when work came while it ran; * due when its next timer is (the monotonic clock in ms, 0: none). */ struct Owner *next; + bool live; /* it takes turns: its app runs */ bool queued, running, again; uint64_t due, turns; size_t queue; /* how many requests may wait for its turn (0: any number) */ @@ -353,15 +376,21 @@ typedef struct Owner { * --apps has its own, the first segment of its routes. */ enum { APP_NAME = 32 }; struct App { - char name[APP_NAME + 1]; - PureModule module; + char name[APP_NAME + 1]; /* never changed once the app is in its place: threads find it by name */ + PureModule *module; /* the module it runs, or ran or was given last (NULL: none loaded) */ + /* The module its requests are resolved with, any thread's to read: NULL + * while it does not run (its routes answer 503 stopped, 404 no_app once + * gone). A module an app ran is kept as long as the host runs: requests + * and snapshots may still point to it. */ + _Atomic(const PureModule *) live; + _Atomic bool gone; const char *store; bool documents; /* the module serves /objects: it has show and edit */ bool owned; /* an owner runs it (states or outputs, or any of --apps); else the connection threads */ uint32_t show, edit; size_t memory; /* its budgets (0: none): what its states hold, */ - uint64_t work; /* what an event or a view's evaluation may run */ - bool running; /* started; else its routes answer 503 stopped, and why says why */ + _Atomic uint64_t work; /* what an event or a view's evaluation may run */ + bool running; /* started (the main thread's word); else why says why */ char why[256]; unsigned home; /* the connection thread its connections move to (a host of apps) */ /* Its wiring in --apps (send=, seal=, reply=), as --send, --seal and --reply give the one app's. */ @@ -370,7 +399,14 @@ struct App { Owner owner; }; static App *apps; -static unsigned app_count; +/* The places taken: an app's place is its own, never another name's, so a + * thread finds an app by name while others start and stop (docs/design/017's + * stage 3). app_slots is how many there are, made when the host starts. */ +static _Atomic unsigned app_count; +static unsigned app_slots = 1; +static unsigned places(void) { return atomic_load_explicit(&app_count, memory_order_acquire); } +/* The modules apps ran before the one they run, kept as long as the host runs. */ +static struct Retired { PureModule *module; struct Retired *next; } *retired_modules; static bool named_apps; /* --apps: a route's first segment names its app */ static Host *all_hosts; /* the connection threads, for moving a connection to its app's home */ static bool moved_home(Connection *c, size_t head_size); @@ -670,7 +706,7 @@ static bool parse_head(Connection *c, char *head, size_t size, char **query) { if (named_apps && *path == '/') { const char *end = strchr(path + 1, '/'); size_t length = end ? (size_t)(end - path - 1) : 0; - for (unsigned i = 0; end && i < app_count && !c->app; ++i) + for (unsigned i = 0, count = places(); end && i < count && !c->app; ++i) if (strlen(apps[i].name) == length && !memcmp(apps[i].name, path + 1, length)) c->app = &apps[i]; if (end) path += 1 + length; } @@ -803,13 +839,15 @@ static const char *resolve(Connection *c, char *query, Route *route, int *status c->field_bytes = 0; memset(c->given, 0, sizeof(c->given)); if (!app) return "no_app"; - if (!app->running) { + const PureModule *m = atomic_load_explicit(&app->live, memory_order_acquire); + if (!m && atomic_load_explicit(&app->gone, memory_order_relaxed)) return "no_app"; + if (!m) { *status = 503; return "stopped"; } - const PureModule *m = &app->module; route->app = app; - if (route->kind == ROUTE_NONE || (route->kind == ROUTE_OBJECT && !app->documents) || + route->module = c->module = m; + if (route->kind == ROUTE_NONE || (route->kind == ROUTE_OBJECT && !m->collections) || (route->kind == ROUTE_QUERY && !app->owned)) return "route"; /* a query reads an app's states */ if (route->kind == ROUTE_OBJECT) return NULL; if (route->kind == ROUTE_QUERY) { /* the view is the query module's, found when shown */ @@ -916,14 +954,14 @@ enum { DELIVERY = 80 }; * into the prefix; strings stay in the body, and the table says where. * NULL, or the error to answer. */ static const char *convert(Connection *c) { - const PureInputShape *shape = &(&c->app->module)->input[c->input]; + const PureInputShape *shape = &c->module->input[c->input]; uint32_t delivered = get_u32(c->ingress) & DELIVERED; size_t table = 8 * (size_t)shape->field_count, payload = c->batch * c->field_bytes + c->body_size; unsigned char *tables = c->ingress + 4 + (delivered ? DELIVERY : 0), *prefixes = tables + c->batch * table; unsigned char *json = c->body; size_t size = c->body_size, at = pure_json_space(json, 0, size); /* A message's rows go after the body: they are encoded, then placed. */ - PureJsonMessage b = {.module = (&c->app->module), .shape = shape, .given = c->given, .rows_base = payload}; + PureJsonMessage b = {.module = c->module, .shape = shape, .given = c->given, .rows_base = payload}; const char *why = NULL; /* A batch is an array of its messages' objects. */ if (c->batch > 1) { @@ -943,7 +981,7 @@ static const char *convert(Connection *c) { * member naming no field is refused all the same: it would be dropped. */ for (uint32_t f = 0; !why && f < shape->field_count; ++f) { if (b.seen[f] || c->given[f]) continue; - const PureField *field = &(&c->app->module)->field[shape->field_start + f]; + const PureField *field = &c->module->field[shape->field_start + f]; if (!delivered || field->type == PURE_FIELD_ROWS) why = "missing_field"; else if (pure_field_fixed(field->type)) memset(b.payload + field->offset, 0, pure_field_size(field->type)); @@ -1062,7 +1100,7 @@ static void start_request(Connection *c, size_t head_size) { /* A document's PUT carries its body; an input's event its payload's size, * the payload (the fixed fields, then the body), and room for its JSON. */ bool write = route.kind == ROUTE_INPUT || c->put; - size_t table = route.kind == ROUTE_INPUT && c->json ? 8 * (size_t)c->app->module.input[c->input].field_count : 0; + size_t table = route.kind == ROUTE_INPUT && c->json ? 8 * (size_t)route.module->input[c->input].field_count : 0; size_t lead = 4 + (route.kind == ROUTE_INPUT && *c->source ? DELIVERY : 0); /* A batch has a table and a prefix for each of its messages. */ size_t input_size = route.kind == ROUTE_INPUT ? lead + c->batch * (table + c->field_bytes) + c->body_size : @@ -1630,12 +1668,12 @@ static void surface_changed(PureSurfaceHost *s) { * saved, so a read made after a write's answer sees the write. A failed save * returns the app to the last save's states, which the snapshot still shows. */ static void publish(Executor *ex) { - if (!ex->publishes || (ex->published && ex->published_changes == ex->app.changes)) return; + if (!ex->published || (ex->publications && ex->published_changes == ex->app.changes)) return; PureSnapshot *made = pure_app_snapshot(&ex->app), *old; - pthread_mutex_lock(&ex->published_lock); - old = ex->published; - ex->published = made; - pthread_mutex_unlock(&ex->published_lock); + pthread_mutex_lock(&ex->published->lock); + old = ex->published->snapshot; + ex->published->snapshot = made; + pthread_mutex_unlock(&ex->published->lock); pure_snapshot_release(old); ex->published_changes = ex->app.changes; ++ex->publications; @@ -1661,6 +1699,8 @@ enum { LOCAL_RETRY_MS = 100 }; typedef struct Local { Owner *from, *to; uint32_t output, input; /* the sender's output, the receiver's input */ + const PureModule *sender, *module; /* the sender's module; the receiver's, which input is in */ + char input_name[PURE_MODULE_NAME + 1], why[160]; /* the input by name; why it holds its sends, as last said */ char source[PURE_SOURCE + 1]; unsigned char fields[PURE_FIELDS_NAMED]; /* the sender's, when the input takes them by name */ size_t fields_size; @@ -1710,14 +1750,56 @@ static bool local_queue(Local *l, uint64_t seq, PureSpan payload) { return room; } +/* The receiver's input the outlet hands to, found by name in its module r: + * one taking messages of the output's shape or, by name, its fields. False, + * why in error, when r has none. */ +static bool local_bind(Local *l, const PureModule *r, char *error, size_t size) { + int input = pure_module_input(r, l->input_name); + if (input < 0 || r->input[input].kind != PURE_KIND_MESSAGE || r->input[input].keyed) { + snprintf(error, size, "no input '%s' taking messages", l->input_name); + return false; + } + size_t fields_size = 0; + if (pure_module_shape(r, (uint32_t)input) != pure_module_shape(l->sender, l->output)) { /* taken by name, or not at all */ + fields_size = pure_module_fields(l->sender, l->output, l->fields); + if (!pure_module_takes(r, (uint32_t)input, l->fields, fields_size)) { + snprintf(error, size, "its input %s does not take the output's fields", l->input_name); + return false; + } + } + l->input = (uint32_t)input; + l->fields_size = fields_size; + l->module = r; + return true; +} + /* The batch queued goes to the receiver, unless one is there or it waits - * to try again; as a link's delivery is laid out (link_deliver). */ + * to try again; as a link's delivery is laid out (link_deliver). A + * receiver that does not run holds it (LOCAL_RETRY_MS), and one that runs + * another module now has its input found again first. */ static void local_hand(Executor *ex, Local *l, uint64_t now) { pthread_mutex_lock(&l->lock); - if (l->out || !l->count || (l->retry && now < l->retry)) { + bool waits = l->out || !l->count || (l->retry && now < l->retry); + pthread_mutex_unlock(&l->lock); + if (waits) return; + Owner *to = l->to; + pthread_mutex_lock(&to->lock); + const PureModule *runs = to->stopping ? NULL : to->module; + pthread_mutex_unlock(&to->lock); + char error[160] = "its receiver does not run"; + if (!runs || (runs != l->module && !local_bind(l, runs, error, sizeof(error)))) { + if (strcmp(error, l->why)) { /* said once a cause */ + snprintf(l->why, sizeof(l->why), "%s", error); + fprintf(stderr, "{\"local\":\"%s\",\"input\":\"%s\",\"error\":\"%s; its sends wait\"}\n", l->source, + l->input_name, error); + } + pthread_mutex_lock(&l->lock); + l->retry = now + LOCAL_RETRY_MS; pthread_mutex_unlock(&l->lock); return; } + l->why[0] = 0; + pthread_mutex_lock(&l->lock); size_t length = strlen(l->source), size = 4 + DELIVERY + l->batch_size + l->fields_size; unsigned char *ingress = malloc(size); LinkJob *link = malloc(sizeof(*link)); @@ -1743,11 +1825,10 @@ static void local_hand(Executor *ex, Local *l, uint64_t now) { l->count = 0; l->retry = 0; pthread_mutex_unlock(&l->lock); - Job job = {.route = {.kind = ROUTE_INPUT, .target = l->input}, .link = link, + Job job = {.route = {.kind = ROUTE_INPUT, .target = l->input, .module = l->module}, .link = link, .effect = {.input = PURE_INPUT_WRITE, .key = "", .expected = -1, .value = {ingress, size}}}; - Owner *to = l->to; pthread_mutex_lock(&to->lock); - bool queued = !to->stopping && grow(&to->jobs, &to->capacity, to->count + 1, sizeof(*to->jobs)); + bool queued = !to->stopping && to->module == l->module && grow(&to->jobs, &to->capacity, to->count + 1, sizeof(*to->jobs)); if (queued) { to->jobs[to->count++] = job; ++to->arrivals; @@ -1763,7 +1844,8 @@ static void local_hand(Executor *ex, Local *l, uint64_t now) { free(link); pthread_mutex_lock(&l->lock); l->out = false; - l->rewind = true; /* queued again from the outbox */ + l->rewind = true; /* queued again from the outbox, after a pause */ + l->retry = now + LOCAL_RETRY_MS; pthread_mutex_unlock(&l->lock); } @@ -2102,14 +2184,14 @@ static void answer_handed(Host *host) { * when there is none. Taken for each round, after the round's answers from * the owner: those were handed back after their saves' snapshots. */ static PureSnapshot *read_published(Host *host, App *app) { - Executor *ex = &app->owner.executor; - pthread_mutex_lock(&ex->published_lock); - PureSnapshot *snapshot = ex->published; + Published *published = &app->owner.published; + pthread_mutex_lock(&published->lock); + PureSnapshot *snapshot = published->snapshot; if (snapshot) pure_snapshot_hold(snapshot); - pthread_mutex_unlock(&ex->published_lock); + pthread_mutex_unlock(&published->lock); PureApp *reader = &host->readers[app - apps]; if (snapshot) pure_app_read(reader, snapshot); - reader->work_budget = app->work; + reader->work_budget = atomic_load_explicit(&app->work, memory_order_relaxed); return snapshot; } @@ -2121,7 +2203,7 @@ static PureSnapshot *read_published(Host *host, App *app) { * holds up, the owner's writes. */ enum { QUERIES = 64 }; typedef struct { PureModule module; unsigned refs; } Held; -static struct { const App *app; char name[65]; Held *held; } queries[QUERIES]; +static struct { const App *app; char name[65]; Held *held; const PureModule *checked; } queries[QUERIES]; static pthread_mutex_t queries_lock = PTHREAD_MUTEX_INITIALIZER; static void let_go_query(Held *held) { @@ -2164,7 +2246,7 @@ static PureResult register_query(const Job *job) { free(held); return PURE_INVALID; } - PureResult status = !same_states(&held->module, &app->module) ? PURE_CONFLICT + PureResult status = !same_states(&held->module, job->route.module) ? PURE_CONFLICT : pure_module_view(&held->module, job->effect.key) < 0 ? PURE_NOT_FOUND : PURE_OK; Held *old = NULL; @@ -2180,6 +2262,7 @@ static PureResult register_query(const Job *job) { else { old = queries[slot].held; queries[slot].app = app; + queries[slot].checked = job->route.module; snprintf(queries[slot].name, sizeof(queries[slot].name), "%s", job->effect.key); queries[slot].held = held; } @@ -2192,10 +2275,13 @@ static PureResult register_query(const Job *job) { static PureResult run_query(const Job *job, PureSnapshot *snapshot, Applied *a) { Held *held = NULL; + const PureModule *checked = NULL; pthread_mutex_lock(&queries_lock); for (int i = 0; i < QUERIES && !held; ++i) - if (queries[i].held && queries[i].app == job->route.app && !strcmp(queries[i].name, job->effect.key)) + if (queries[i].held && queries[i].app == job->route.app && !strcmp(queries[i].name, job->effect.key)) { (held = queries[i].held)->refs++; + checked = queries[i].checked; + } pthread_mutex_unlock(&queries_lock); if (!held) return PURE_NOT_FOUND; if (!snapshot) { @@ -2204,7 +2290,12 @@ static PureResult run_query(const Job *job, PureSnapshot *snapshot, Applied *a) } PureApp reader = {0}; pure_app_read(&reader, snapshot); - pure_app_work(&reader, job->route.app->work); + if (reader.module != checked && !same_states(&held->module, reader.module)) { /* its app's states are laid out anew */ + pure_app_stop(&reader); + let_go_query(held); + return PURE_CONFLICT; + } + pure_app_work(&reader, atomic_load_explicit(&job->route.app->work, memory_order_relaxed)); reader.module = &held->module; /* the app's states, the query's views */ Route route = job->route; route.target = (uint32_t)pure_module_view(&held->module, job->effect.key); @@ -2263,6 +2354,11 @@ static void serve_bursts(Host *host) { for (size_t k = i; k < count; ++k) { const Job *mine = &host->jobs[k]; if (mine->route.app != app || shown_here(host, mine)) continue; + if (owner->stopping || mine->route.module != owner->module) { /* its app stopped, or runs another module now */ + host->views[answers++] = (Applied){.host = host, .ticket = mine->effect.ticket, .input = mine->effect.input, + .route = mine->route.kind, .status = PURE_CLOSED, .refused = "stopped"}; + continue; + } if (owner->queue && owner->count >= owner->queue) { host->views[answers++] = (Applied){.host = host, .ticket = mine->effect.ticket, .input = mine->effect.input, .route = mine->route.kind, .status = PURE_BUSY, .refused = "busy"}; @@ -2289,6 +2385,11 @@ static void serve_bursts(Host *host) { bool registering = job->route.kind == ROUTE_QUERY && job->effect.input == PURE_INPUT_WRITE; *applied = (Applied){.host = host, .ticket = job->effect.ticket, .input = job->effect.input, .route = registering ? ROUTE_QUERY : ROUTE_VIEW}; + if (job->route.kind == ROUTE_VIEW && host->readers[a].module != job->route.module) { /* another module's view now */ + applied->status = PURE_CLOSED; + applied->refused = "stopped"; + continue; + } applied->status = job->route.kind == ROUTE_VIEW ? show_view(&host->readers[a], &job->route, job->effect.available, applied) : registering ? register_query(job) : run_query(job, host->snapshots[a], applied); @@ -2336,7 +2437,8 @@ static void enqueue(Owner *owner) { * another after it). Any thread, holding none of the owner's locks. */ static void schedule(Owner *owner) { pthread_mutex_lock(&turns.lock); - if (owner->running) owner->again = true; + if (!owner->live) { /* its app stopped: what it did not take waits for its next start */ } + else if (owner->running) owner->again = true; else if (!owner->queued) enqueue(owner); pthread_mutex_unlock(&turns.lock); } @@ -2433,9 +2535,9 @@ static bool owner_turn(Owner *owner, bool alone, long *due) { * due of the others (UINT64_MAX: none). */ static uint64_t enqueue_due(uint64_t now) { uint64_t soonest = UINT64_MAX; - for (unsigned i = 0; i < app_count; ++i) { + for (unsigned i = 0, count = places(); i < count; ++i) { Owner *o = &apps[i].owner; - if (!apps[i].running || !apps[i].owned || !o->due || o->queued || o->running) continue; + if (!o->live || !o->due || o->queued || o->running) continue; if (o->due <= now) { o->due = 0; enqueue(o); @@ -2499,11 +2601,11 @@ static void *run_worker(void *context) { /* The surface's table (serve/surface.h). */ static PureSnapshot *surface_read(PureSurfaceHost *s, PureApp *reader) { - Executor *ex = &s->owner->executor; - pthread_mutex_lock(&ex->published_lock); - PureSnapshot *snapshot = ex->published; + Published *published = &s->owner->published; + pthread_mutex_lock(&published->lock); + PureSnapshot *snapshot = published->snapshot; if (snapshot) pure_snapshot_hold(snapshot); - pthread_mutex_unlock(&ex->published_lock); + pthread_mutex_unlock(&published->lock); if (snapshot) pure_app_read(reader, snapshot); return snapshot; } @@ -2609,33 +2711,53 @@ static bool replies_borrow(const PureModule *m, uint32_t input) { /* ---- Main ------------------------------------------------------------- */ -/* An app's module from its file, and whether it serves documents; false, - * with why, when it does not load or cannot be served. */ +/* A module from its file for an app, which serves documents when it keeps + * a keyed collection; false, with why, when it does not load or cannot be + * served. The module the app had is kept (see retired_modules). */ static bool load_module(App *app, const char *path) { - PureModule *m = &app->module; - bool *documents = &app->documents; + PureModule *m = calloc(1, sizeof(*m)); + bool documents; char error[160]; + if (!m) { + snprintf(app->why, sizeof(app->why), "Module: out of memory"); + return false; + } if (pure_module_read(path, m, error, sizeof(error)) != PURE_OK) { snprintf(app->why, sizeof(app->why), "Module: %s", error); + free(m); return false; } /* Documents are served when the module keeps a keyed collection: its keyed * messages show and edit read and write one by key. Without one, inputs * named show and edit are messages as any other. */ int show = pure_module_input(m, "show"), edit = pure_module_input(m, "edit"); - *documents = m->collections > 0; - if (*documents && (show < 0 || edit < 0 || m->input[show].kind != PURE_KIND_MESSAGE || !m->input[show].keyed || - m->input[edit].kind != PURE_KIND_MESSAGE || !m->input[edit].keyed)) { - snprintf(app->why, sizeof(app->why), "Module: documents need keyed messages named show and edit"); - return false; - } - for (uint32_t i = 0; i < m->inputs; ++i) { - bool served = (*documents && i == (uint32_t)edit) || (m->input[i].kind == PURE_KIND_MESSAGE && !m->input[i].keyed); - if (served && !replies_borrow(m, i)) { + documents = m->collections > 0; + bool served = !documents || (show >= 0 && edit >= 0 && m->input[show].kind == PURE_KIND_MESSAGE && m->input[show].keyed && + m->input[edit].kind == PURE_KIND_MESSAGE && m->input[edit].keyed); + if (!served) snprintf(app->why, sizeof(app->why), "Module: documents need keyed messages named show and edit"); + for (uint32_t i = 0; served && i < m->inputs; ++i) { + bool replied = (documents && i == (uint32_t)edit) || (m->input[i].kind == PURE_KIND_MESSAGE && !m->input[i].keyed); + if (replied && !replies_borrow(m, i)) { snprintf(app->why, sizeof(app->why), "Module: %s may reply only a field of its message", m->input_names[i]); - return false; + served = false; } } + struct Retired *kept = served && app->module ? malloc(sizeof(*kept)) : NULL; + if (served && app->module && !kept) { + snprintf(app->why, sizeof(app->why), "Module: out of memory"); + served = false; + } + if (!served) { + pure_module_release(m); + free(m); + return false; + } + if (kept) { /* requests and snapshots may still point to it */ + *kept = (struct Retired){app->module, retired_modules}; + retired_modules = kept; + } + app->module = m; + app->documents = documents; app->show = (uint32_t)(show >= 0 ? show : 0); app->edit = (uint32_t)(edit >= 0 ? edit : 0); return true; @@ -2703,7 +2825,7 @@ static bool moved_home(Connection *c, size_t head_size) { while (end < c->in + head_size && *end != '/' && *end != ' ' && *end != '?') ++end; size_t length = (size_t)(end - path - 1); App *app = NULL; - for (unsigned i = 0; i < app_count && !app; ++i) + for (unsigned i = 0, count = places(); i < count && !app; ++i) if (strlen(apps[i].name) == length && !memcmp(apps[i].name, path + 1, length)) app = &apps[i]; Host *home = app ? &all_hosts[app->home] : NULL; if (!home || home == c->host) return false; @@ -2756,14 +2878,14 @@ static void take_moved(Host *host) { /* An app's module on its store, with its budgets; false (why says why) * when the store does not open or the module does not start on it. */ static bool start_executor(Executor *ex, App *app) { - ex->module = &app->module; + ex->module = app->module; ex->show = app->show; ex->edit = app->edit; if (pure_store_open(app->store, &ex->store) != PURE_OK) { snprintf(app->why, sizeof(app->why), "Cannot open the store"); return false; } - if (pure_app_start(&ex->app, &app->module, pure_store_data(ex->store)) != PURE_OK || !pure_app_defer(&ex->app, true)) { + if (pure_app_start(&ex->app, app->module, pure_store_data(ex->store)) != PURE_OK || !pure_app_defer(&ex->app, true)) { snprintf(app->why, sizeof(app->why), "Cannot start the module on the store%s%s", ex->app.why[0] ? ": " : "", ex->app.why); pure_store_close(ex->store); @@ -2778,7 +2900,13 @@ static bool start_executor(Executor *ex, App *app) { static void stop_executor(Executor *ex) { stop_writer(ex); - pure_snapshot_release(ex->published); + if (ex->published) { /* views are the owner's again, which shows none */ + pthread_mutex_lock(&ex->published->lock); + PureSnapshot *last = ex->published->snapshot; + ex->published->snapshot = NULL; + pthread_mutex_unlock(&ex->published->lock); + pure_snapshot_release(last); + } pure_app_stop(&ex->app); pure_store_close(ex->store); free(ex->applied); @@ -2793,11 +2921,12 @@ static bool start_shard(Host *host, size_t budget, unsigned *port) { fprintf(stderr, "%s\n", apps[0].why); return false; } - host->readers = calloc(app_count, sizeof(*host->readers)); - host->snapshots = calloc(app_count, sizeof(*host->snapshots)); - host->read_in = calloc(app_count, sizeof(*host->read_in)); - host->handed_in = calloc(app_count, sizeof(*host->handed_in)); - host->touched = calloc(app_count, sizeof(*host->touched)); + /* By app place: the places not taken are memory never touched. */ + host->readers = calloc(app_slots, sizeof(*host->readers)); + host->snapshots = calloc(app_slots, sizeof(*host->snapshots)); + host->read_in = calloc(app_slots, sizeof(*host->read_in)); + host->handed_in = calloc(app_slots, sizeof(*host->handed_in)); + host->touched = calloc(app_slots, sizeof(*host->touched)); if (!host->readers || !host->snapshots || !host->read_in || !host->handed_in || !host->touched) return false; host->work = calloc(host->capacity, sizeof(*host->work)); host->table = calloc(host->capacity, sizeof(*host->table)); @@ -2898,7 +3027,7 @@ static void *run_shard(void *context) { static void stop_shard(Host *host) { if (!apps[0].owned) stop_executor(&host->local); - for (unsigned i = 0; host->readers && i < app_count; ++i) pure_app_stop(&host->readers[i]); + for (unsigned i = 0, count = places(); host->readers && i < count; ++i) pure_app_stop(&host->readers[i]); free(host->readers); free(host->snapshots); free(host->read_in); @@ -2932,25 +3061,36 @@ static void stop_shard(Host *host) { /* The input a link's stream goes to: one taking messages (not keyed) of the * sender's shape, or, named its fields, one that takes them by name * (pure_module_takes: each record is laid out anew for it). */ -/* In a host of apps the name is APP/INPUT; the input found is the app's - * place times PURE_MODULE_INPUTS, plus the input's. */ +/* In a host of apps the name is APP/INPUT. What it finds is a binding: the + * app's place, the module it runs and the input in it, the listener + * thread's alone. A delivery whose app runs another module now, or none, is + * not taken: the receiver keeps it and finds the input again once told + * (pure_link_listener_rebind, when the app starts). */ +static struct Binding { unsigned app; const PureModule *module; uint32_t input; } *bindings; +static size_t binding_count, binding_capacity; static int link_input(void *context, const char *name, uint32_t shape, const unsigned char *fields, size_t size) { (void)context; unsigned a = 0; if (named_apps) { const char *slash = strchr(name, '/'); size_t length = slash ? (size_t)(slash - name) : 0; - for (a = 0; a < app_count && (strlen(apps[a].name) != length || memcmp(apps[a].name, name, length)); ++a) {} - if (a == app_count || !apps[a].running) return -PURE_LINK_NO_INPUT; + unsigned count = places(); + for (a = 0; a < count && (strlen(apps[a].name) != length || memcmp(apps[a].name, name, length)); ++a) {} + if (a == count) return -PURE_LINK_NO_INPUT; name = slash + 1; } - const PureModule *m = &apps[a].module; - int index = pure_module_input(m, name); + const PureModule *m = atomic_load_explicit(&apps[a].live, memory_order_acquire); + int index = m ? pure_module_input(m, name) : -1; if (index < 0 || m->input[index].kind != PURE_KIND_MESSAGE || m->input[index].keyed) return -PURE_LINK_NO_INPUT; - int found = (int)(a * PURE_MODULE_INPUTS) + index; - if (pure_module_shape(m, (uint32_t)index) == shape) return found; - if (!fields) return -PURE_LINK_OTHER_SHAPE; - return pure_module_takes(m, (uint32_t)index, fields, size) ? found : -PURE_LINK_FIELDS_LACKED; + if (pure_module_shape(m, (uint32_t)index) != shape && !fields) return -PURE_LINK_OTHER_SHAPE; + if (pure_module_shape(m, (uint32_t)index) != shape && !pure_module_takes(m, (uint32_t)index, fields, size)) + return -PURE_LINK_FIELDS_LACKED; + for (size_t b = 0; b < binding_count; ++b) + if (bindings[b].app == a && bindings[b].module == m && bindings[b].input == (uint32_t)index) return (int)b; + if (binding_count > INT32_MAX - 1 || !grow(&bindings, &binding_capacity, binding_count + 1, sizeof(*bindings))) + return -PURE_LINK_NO_INPUT; + bindings[binding_count] = (struct Binding){a, m, (uint32_t)index}; + return (int)binding_count++; } /* Records a link delivered, queued at the owner as a request is: applied @@ -2958,7 +3098,8 @@ static int link_input(void *context, const char *name, uint32_t shape, const uns static PureLinkListener *link_listener; static bool link_deliver(void *context, const PureLinkDelivery *d) { (void)context; - Owner *owner = &apps[d->input / PURE_MODULE_INPUTS].owner; + const struct Binding *bound = &bindings[d->input]; + Owner *owner = &apps[bound->app].owner; size_t size = 4 + DELIVERY + d->size + d->fields_size, length = strlen(d->source); unsigned char *ingress = malloc(size); LinkJob *link = malloc(sizeof(*link)); @@ -2976,10 +3117,11 @@ static bool link_deliver(void *context, const PureLinkDelivery *d) { memcpy(ingress + 4 + DELIVERY, d->records, d->size); if (d->fields_size) memcpy(ingress + 4 + DELIVERY + d->size, d->fields, d->fields_size); /* laid out with those */ *link = (LinkJob){.listener = link_listener, .token = d->token, .ingress = ingress}; - Job job = {.route = {.kind = ROUTE_INPUT, .target = d->input % PURE_MODULE_INPUTS}, .link = link, + Job job = {.route = {.kind = ROUTE_INPUT, .target = bound->input, .module = bound->module}, .link = link, .effect = {.input = PURE_INPUT_WRITE, .key = "", .expected = -1, .value = {ingress, size}}}; pthread_mutex_lock(&owner->lock); - bool queued = !owner->stopping && grow(&owner->jobs, &owner->capacity, owner->count + 1, sizeof(*owner->jobs)); + bool queued = !owner->stopping && owner->module == bound->module && + grow(&owner->jobs, &owner->capacity, owner->count + 1, sizeof(*owner->jobs)); if (queued) { owner->jobs[owner->count++] = job; ++owner->arrivals; @@ -3009,19 +3151,15 @@ static unsigned char own_public[PURE_SEAL_KEY]; /* this node's key's public half * another app of it (Local), its input taking the output's shape or, by * name, its fields. */ static bool open_local(App *app, uint32_t output, const char *to, const char *source, char *error, size_t size) { + *error = 0; const char *slash = strchr(to, '/'); size_t length = slash ? (size_t)(slash - to) : 0; App *receiver = NULL; - for (unsigned a = 0; named_apps && slash && a < app_count && !receiver; ++a) - if (strlen(apps[a].name) == length && !memcmp(apps[a].name, to, length)) receiver = &apps[a]; - if (!receiver || !receiver->running || !receiver->owned) { - snprintf(error, size, "no running app '%.*s' in this host", (int)length, to); - return false; - } - const PureModule *m = &app->module, *r = &receiver->module; - int input = pure_module_input(r, slash + 1); - if (input < 0 || r->input[input].kind != PURE_KIND_MESSAGE || r->input[input].keyed) { - snprintf(error, size, "%s has no input '%s' taking messages", receiver->name, slash + 1); + for (unsigned a = 0, count = places(); named_apps && slash && a < count && !receiver; ++a) + if (strlen(apps[a].name) == length && !memcmp(apps[a].name, to, length) && !atomic_load(&apps[a].gone)) + receiver = &apps[a]; + if (!receiver || !receiver->owned || strlen(slash + 1) > PURE_MODULE_NAME) { + snprintf(error, size, "no app '%.*s' in this host", (int)length, to); return false; } Executor *ex = &app->owner.executor; @@ -3030,23 +3168,17 @@ static bool open_local(App *app, uint32_t output, const char *to, const char *so snprintf(error, size, "no memory"); return false; } - *l = (Local){.from = &app->owner, .to = &receiver->owner, .output = output, .input = (uint32_t)input}; - if (pthread_mutex_init(&l->lock, NULL)) { - free(l); - snprintf(error, size, "no lock"); - return false; - } + *l = (Local){.from = &app->owner, .to = &receiver->owner, .output = output, .sender = app->module}; + strcpy(l->input_name, slash + 1); size_t named = strnlen(source, PURE_SOURCE); memcpy(l->source, source, named); l->source[named] = 0; - if (pure_module_shape(r, (uint32_t)input) != pure_module_shape(m, output)) { /* taken by name, or not at all */ - l->fields_size = pure_module_fields(m, output, l->fields); - if (!pure_module_takes(r, (uint32_t)input, l->fields, l->fields_size)) { - pthread_mutex_destroy(&l->lock); - free(l); - snprintf(error, size, "%s's input %s does not take the output's fields", receiver->name, slash + 1); - return false; - } + /* A receiver that runs is checked now; one that does not, when it does + * (its sends wait meanwhile, as a link's wait for its node). */ + if ((receiver->running && !local_bind(l, receiver->module, error, size)) || pthread_mutex_init(&l->lock, NULL)) { + if (!*error) snprintf(error, size, "no lock"); + free(l); + return false; } ex->locals[ex->local_count++] = l; return true; @@ -3054,7 +3186,7 @@ static bool open_local(App *app, uint32_t output, const char *to, const char *so static bool open_wires(App *app, const char *name) { Executor *ex = &app->owner.executor; - const PureModule *m = &app->module; + const PureModule *m = app->module; const char *const *sends = app->sends; unsigned count = app->send_count; for (unsigned i = 0; i < count; ++i) { @@ -3239,14 +3371,15 @@ static bool wire_replies(Executor *ex, const PureModule *m, const char *const *r * published; false (why says why) when the app cannot start. */ static bool start_owner(App *app, Host *hosts, unsigned threads) { Owner *owner = &app->owner; - if (!start_executor(&owner->executor, app)) return false; - if (pthread_mutex_init(&owner->lock, NULL) || pure_cond_monotonic(&owner->waiting) || - pthread_mutex_init(&owner->executor.published_lock, NULL)) { + if (!owner->made && (pthread_mutex_init(&owner->lock, NULL) || pure_cond_monotonic(&owner->waiting) || + pthread_mutex_init(&owner->published.lock, NULL))) { snprintf(app->why, sizeof(app->why), "Cannot make the owner's locks"); return false; } + owner->made = true; + if (!start_executor(&owner->executor, app)) return false; owner->executor.reads_first = true; - owner->executor.publishes = true; + owner->executor.published = &owner->published; owner->hosts = hosts; owner->host_count = threads; return true; @@ -3320,8 +3453,7 @@ static bool read_apps(const char *path) { app->owned = true; /* every app of a host of apps takes turns on the workers */ if (!app->store) ok = false; else ++app_count; - if (ok && !load_module(app, words[1])) app->module = (PureModule){0}; /* stopped, with why */ - else if (ok) app->running = true; /* until its store says otherwise */ + if (ok && load_module(app, words[1])) app->running = true; /* until its store says otherwise; else stopped, with why */ } fclose(file); if (ok && !app_count) fprintf(stderr, "--apps %s: no app\n", path); @@ -3362,7 +3494,7 @@ int main(int argc, char **argv) { const char *surface_path = NULL, *module_path = NULL, *store = NULL, *name = NULL, *sends[PURE_MODULE_INPUTS], *replies[PURE_MODULE_INPUTS]; const char *seals[PURE_MODULE_INPUTS], *named[PURE_MODULE_INPUTS], *key_path = NULL, *senders_path = NULL, *apps_path = NULL; unsigned send_count = 0, reply_count = 0, seal_count = 0, named_count = 0; - unsigned port = 8767, threads = 1, link_port = 0, workers = 0; + unsigned port = 8767, threads = 1, link_port = 0, workers = 0, slots = 256; size_t budget = 64u << 20, capacity = 1024; uint64_t timeout_ms = 10000, link_idle_ms = 0; for (int i = 1; i < options; i += 2) { @@ -3392,6 +3524,7 @@ int main(int argc, char **argv) { else if (!strcmp(argv[i], "--threads") && !strcmp(argv[i + 1], "auto")) threads = 0; else if (!strcmp(argv[i], "--threads") && number(argv[i + 1], 256, &value) && value) threads = (unsigned)value; else if (!strcmp(argv[i], "--workers") && number(argv[i + 1], 256, &value) && value) workers = (unsigned)value; + else if (!strcmp(argv[i], "--app-slots") && number(argv[i + 1], 65536, &value) && value) slots = (unsigned)value; else if (!strcmp(argv[i], "--timeout-ms") && number(argv[i + 1], 3600000, &value) && value) timeout_ms = value; else goto usage; } @@ -3407,6 +3540,13 @@ int main(int argc, char **argv) { return 2; } if (!read_apps(apps_path)) return 2; + /* Each app in a place of its own, room left for apps started later. */ + app_slots = slots > app_count ? slots : app_count; + App *placed = calloc(app_slots, sizeof(*placed)); + if (!placed) return 1; + memcpy(placed, apps, app_count * sizeof(*apps)); + free(apps); + apps = placed; named_apps = true; } else { if (!module_path || !store || !(apps = calloc(1, sizeof(*apps)))) goto usage; @@ -3421,8 +3561,8 @@ int main(int argc, char **argv) { /* States (and outputs, whose sends are the app's state) have one * owner, whose snapshots the connection threads read. */ bool outputs = false; - for (uint32_t i = 0; i < apps[0].module.inputs; ++i) outputs = outputs || apps[0].module.input[i].kind == PURE_KIND_OUTPUT; - apps[0].owned = apps[0].module.states || outputs; + for (uint32_t i = 0; i < apps[0].module->inputs; ++i) outputs = outputs || apps[0].module->input[i].kind == PURE_KIND_OUTPUT; + apps[0].owned = apps[0].module->states || outputs; apps[0].running = true; } if (!threads) threads = auto_threads(apps[0].owned); @@ -3491,8 +3631,8 @@ int main(int argc, char **argv) { Executor *ex = &app->owner.executor; if (!app->running || !app->owned) continue; if (open_wires(app, named_apps ? app->name : name) && - wire_replies(ex, &app->module, app->replies, app->reply_count) && - wire_seals(ex, &app->module, app->seals, app->seal_count, seal_key ? own_public : NULL)) + wire_replies(ex, app->module, app->replies, app->reply_count) && + wire_seals(ex, app->module, app->seals, app->seal_count, seal_key ? own_public : NULL)) continue; if (!named_apps) return 2; snprintf(app->why, sizeof(app->why), "Its sends are wired wrong (said above)"); @@ -3504,8 +3644,16 @@ int main(int argc, char **argv) { app->running = false; --owners; } - for (unsigned a = 0; a < app_count; ++a) - if (apps[a].running && apps[a].owned) after_save(&apps[a].owner.executor); /* the snapshot, and the sends stored before */ + for (unsigned a = 0; a < app_count; ++a) { + App *app = &apps[a]; + if (!app->running) continue; + if (app->owned) { /* it takes requests resolved with its module, and turns */ + app->owner.module = app->module; + app->owner.live = true; + after_save(&app->owner.executor); /* the snapshot, and the sends stored before */ + } + atomic_store(&app->live, app->module); + } for (unsigned a = 0; a < app_count; ++a) { Owner *o = &apps[a].owner; if (!apps[a].running || !apps[a].owned) continue; |
