diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2016-02-23 10:16:57 -0500 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2016-02-23 10:16:57 -0500 |
commit | 423931219dd3e3b669fde97786cadae92c066dc1 (patch) | |
tree | 4e433f105c8af6eb25cb2635acae5ca79878d40c | |
parent | c758cdf4f6e959b92683f2dba6ce8617ac4f0a83 (diff) | |
download | jack1-423931219dd3e3b669fde97786cadae92c066dc1.tar.gz |
use topological sort for client ordering (fons adriaensen)
-rw-r--r-- | include/engine.h | 7 | ||||
-rw-r--r-- | include/internal.h | 28 | ||||
-rw-r--r-- | jackd/clientengine.c | 40 | ||||
-rw-r--r-- | jackd/engine.c | 922 |
4 files changed, 514 insertions, 483 deletions
diff --git a/include/engine.h b/include/engine.h index 5106713..3e869af 100644 --- a/include/engine.h +++ b/include/engine.h @@ -144,6 +144,13 @@ struct _jack_engine { JSList *clients_waiting; JSList *reserved_client_names; + /* Double linked list of clients in running order */ + jack_client_internal_t *execlist_head; + jack_client_internal_t *execlist_tail; +#define EXECLIST_ORDER 1 /* Re-order clients */ +#define EXECLIST_CYCLE 2 /* Check cycles */ + int execlist_request; + jack_port_internal_t *internal_ports; jack_client_internal_t *timebase_client; jack_port_buffer_info_t *silent_buffer; diff --git a/include/internal.h b/include/internal.h index ed2202d..1bff379 100644 --- a/include/internal.h +++ b/include/internal.h @@ -452,6 +452,18 @@ struct _jack_request { int32_t status; } POST_PACKED_STRUCTURE; + +/* Data used by the algorithm determining running order + * of clients. + */ +typedef struct _jack_feedcounts { + struct _jack_feedcounts *next; + struct _jack_client_internal *client; + int fwd_count; + int rev_count; +} jack_feedcounts_t; + + /* Per-client structure allocated in the server's address space. * It's here because its not part of the engine structure. */ @@ -464,14 +476,18 @@ typedef struct _jack_client_internal { int event_fd; int subgraph_start_fd; int subgraph_wait_fd; - JSList *ports; /* protected by engine->client_lock */ - JSList *truefeeds; /* protected by engine->client_lock */ - JSList *sortfeeds; /* protected by engine->client_lock */ - int fedcount; - int tfedcount; + + /* protected by engine->client_lock */ + JSList *ports; + + jack_feedcounts_t *feedlist; + struct _jack_client_internal *execlist_next; + struct _jack_client_internal *execlist_prev; + int depcount; + int depcheck; + jack_shm_info_t control_shm; unsigned long execution_order; - struct _jack_client_internal *next_client; /* not a linked list! */ dlhandle handle; int (*initialize)(jack_client_t*, const char*); /* int. clients only */ void (*finish)(void *); /* internal clients only */ diff --git a/jackd/clientengine.c b/jackd/clientengine.c index 365e287..8f47894 100644 --- a/jackd/clientengine.c +++ b/jackd/clientengine.c @@ -62,13 +62,13 @@ jack_client_disconnect_ports (jack_engine_t *engine, } jack_slist_free (client->ports); - jack_slist_free (client->truefeeds); - jack_slist_free (client->sortfeeds); - client->truefeeds = 0; - client->sortfeeds = 0; client->ports = 0; } + +/* Modified for new client execution order algorithm. + * FA 08/2015 + */ int jack_client_do_deactivate (jack_engine_t *engine, jack_client_internal_t *client, int sort_graph) @@ -88,6 +88,7 @@ jack_client_do_deactivate (jack_engine_t *engine, } if (sort_graph) { + engine->execlist_request = EXECLIST_ORDER | EXECLIST_CYCLE; jack_sort_graph (engine); } return 0; @@ -162,7 +163,6 @@ jack_zombify_client (jack_engine_t *engine, jack_client_internal_t *client) client->control->name); /* caller must hold the client_lock */ - /* this stops jack_deliver_event() from contacing this client */ client->control->dead = TRUE; @@ -337,6 +337,9 @@ jack_check_clients (jack_engine_t* engine, int with_timeout_check) return errs; } +/* Modified for new client execution order algorithm. + * FA 08/2015 + */ void jack_remove_clients (jack_engine_t* engine, int* exit_freewheeling_when_done) { @@ -404,6 +407,7 @@ jack_remove_clients (jack_engine_t* engine, int* exit_freewheeling_when_done) } if (need_sort) { + engine->execlist_request = EXECLIST_ORDER | EXECLIST_CYCLE; jack_sort_graph (engine); } @@ -564,6 +568,9 @@ jack_client_name_invalid (jack_engine_t *engine, char *name, /* Set up the engine's client internal and control structures for both * internal and external clients. */ +/* Modified for new client execution order algorithm. + * FA 08/2015 + */ static jack_client_internal_t * jack_setup_client_control (jack_engine_t *engine, int fd, ClientType type, const char *name, jack_uuid_t uuid) { @@ -575,10 +582,11 @@ jack_setup_client_control (jack_engine_t *engine, int fd, ClientType type, const client->request_fd = fd; client->event_fd = -1; client->ports = 0; - client->truefeeds = 0; - client->sortfeeds = 0; + client->feedlist = 0; + client->depcount = 0; + client->depcheck = 0; + client->execlist_next = 0; client->execution_order = UINT_MAX; - client->next_client = NULL; client->handle = NULL; client->finish = NULL; client->error = 0; @@ -764,19 +772,15 @@ setup_client (jack_engine_t *engine, ClientType type, char *name, jack_engine_reset_rolling_usecs (engine); if (jack_client_is_internal (client)) { - - jack_unlock_graph (engine); /* Call its initialization function. This function * may make requests of its own, so we temporarily * release and then reacquire the request_lock. */ if (client->control->type == ClientInternal) { - pthread_mutex_unlock (&engine->request_lock); if (client->initialize (client->private_client, object_data)) { - /* failed: clean up client data */ VERBOSE (engine, "%s jack_initialize() failed!", @@ -792,8 +796,7 @@ setup_client (jack_engine_t *engine, ClientType type, char *name, pthread_mutex_lock (&engine->request_lock); } - } else { /* external client */ - + } else { jack_unlock_graph (engine); } @@ -864,6 +867,7 @@ jack_get_reserved_name (jack_engine_t *engine, jack_uuid_t uuid) } return 0; } + int jack_client_create (jack_engine_t *engine, int client_fd) { @@ -973,6 +977,10 @@ jack_client_create (jack_engine_t *engine, int client_fd) return 0; } + +/* Modified for new client execution order algorithm. + * FA 08/2015 + */ int jack_client_activate (jack_engine_t *engine, jack_uuid_t id) { @@ -997,8 +1005,8 @@ jack_client_activate (jack_engine_t *engine, jack_uuid_t id) * this point. */ - jack_get_fifo_fd (engine, - ++engine->external_client_cnt); + jack_get_fifo_fd (engine, ++engine->external_client_cnt); + engine->execlist_request = EXECLIST_ORDER; jack_sort_graph (engine); diff --git a/jackd/engine.c b/jackd/engine.c index c6b2e60..335dc81 100644 --- a/jackd/engine.c +++ b/jackd/engine.c @@ -71,7 +71,6 @@ typedef struct { jack_port_internal_t *source; jack_port_internal_t *destination; - signed int dir; /* -1 = feedback, 0 = self, 1 = forward */ jack_client_internal_t *srcclient; jack_client_internal_t *dstclient; } jack_connection_internal_t; @@ -127,11 +126,6 @@ static void jack_engine_delay(jack_engine_t *engine, float delayed_usecs); static void jack_engine_driver_exit(jack_engine_t* engine); static int jack_start_freewheeling(jack_engine_t* engine, jack_uuid_t); -static int jack_client_feeds_transitive(jack_client_internal_t *source, - jack_client_internal_t *dest); -static int jack_client_sort(jack_client_internal_t *a, - jack_client_internal_t *b); -static void jack_check_acyclic(jack_engine_t* engine); static void jack_compute_all_port_total_latencies(jack_engine_t *engine); static void jack_compute_port_total_latency(jack_engine_t *engine, jack_port_shared_t*); static int jack_check_client_status(jack_engine_t* engine); @@ -572,22 +566,21 @@ jack_set_buffer_size_request (jack_engine_t *engine, jack_nframes_t nframes) } -static JSList * -jack_process_internal (jack_engine_t *engine, JSList *node, +/* Modified to use the new double-linked list of clients in execution order + * FA 08/2015 + */ +static jack_client_internal_t* +jack_process_internal (jack_engine_t *engine, jack_client_internal_t *client, jack_nframes_t nframes) { - jack_client_internal_t *client; jack_client_control_t *ctl; - client = (jack_client_internal_t*)node->data; ctl = client->control; - - /* internal client */ - - DEBUG ("invoking an internal client's (%s) callbacks", ctl->name); ctl->state = Running; engine->current_client = client; + DEBUG ("invoking an internal client's (%s) callbacks", ctl->name); + /* XXX how to time out an internal client? */ if (ctl->sync_cb_cbset) { @@ -608,12 +601,11 @@ jack_process_internal (jack_engine_t *engine, JSList *node, ctl->state = Finished; if (engine->process_errors) { - return NULL; /* will stop the loop */ - } else { - return jack_slist_next (node); - } + return NULL; + } else { return client->execlist_next; } } + #ifdef __linux /* Linux kernels somewhere between 2.6.18 and 2.6.24 had a bug @@ -650,18 +642,22 @@ linux_poll_bug_encountered (jack_engine_t* engine, jack_time_t then, jack_time_t } #endif + #ifdef JACK_USE_MACH_THREADS -static JSList * -jack_process_external (jack_engine_t *engine, JSList *node) + +/* Modified to use the new double-linked list of clients in execution order. + * Unlike the Linux version, this just returns the next client in the list, + * as did the original. I'm not sure if I understand how this works and + * can't test it, but it seems to be correct. + * FA 08/2015 + */ +static jack_client_internal * +jack_process_external (jack_engine_t *engine, jack_client_internal_t *client) { - jack_client_internal_t * client = (jack_client_internal_t*)node->data; jack_client_control_t *ctl; - client = (jack_client_internal_t*)node->data; - ctl = client->control; - engine->current_client = client; - + ctl = client->control; // a race exists if we do this after the write(2) ctl->state = Triggered; ctl->signalled_at = jack_get_microseconds (); @@ -671,35 +667,32 @@ jack_process_external (jack_engine_t *engine, JSList *node) ctl->state = Finished; } - return jack_slist_next (node); + return client->execlist_next; } + #else /* !JACK_USE_MACH_THREADS */ -static JSList * -jack_process_external (jack_engine_t *engine, JSList *node) + +/* Modified to use the new double-linked list of clients in execution order. + * FA 08/2015 + */ +static jack_client_internal_t * +jack_process_external (jack_engine_t *engine, jack_client_internal_t *client) { int status = 0; char c = 0; struct pollfd pfd[1]; int poll_timeout; jack_time_t poll_timeout_usecs; - jack_client_internal_t *client; jack_client_control_t *ctl; jack_time_t now, then; int pollret; - client = (jack_client_internal_t*)node->data; - + engine->current_client = client; ctl = client->control; - - /* external subgraph */ - /* a race exists if we do this after the write(2) */ ctl->state = Triggered; - ctl->signalled_at = jack_get_microseconds (); - engine->current_client = client; - DEBUG ("calling process() on an external subgraph, fd==%d", client->subgraph_start_fd); @@ -744,22 +737,16 @@ again: } if (pfd[0].revents & POLLIN) { - status = 0; } else if (status == 0) { - - /* no events, no errors, we woke up because poll() - decided that time was up ... - */ - + /* no events, no errors, we woke up because poll() decided that time was up ... */ if (engine->freewheeling) { if (jack_check_client_status (engine)) { return NULL; } else { /* all clients are fine - we're just not done yet. since - we're freewheeling, that is fine. - */ + we're freewheeling, that is fine. */ goto again; } } @@ -773,7 +760,6 @@ again: VERBOSE (engine, "FALSE WAKEUP skipped, remaining = %lld usec", poll_timeout_usecs); } else { #endif - jack_error ("subgraph starting at %s timed out " "(subgraph_wait_fd=%d, status = %d, state = %s, pollret = %d revents = 0x%x)", client->control->name, @@ -805,7 +791,6 @@ again: ctl->signalled_at) : 0); if (jack_check_clients (engine, 1)) { - engine->process_errors++; return NULL; /* will stop the loop */ } @@ -830,58 +815,57 @@ again: } /* Move to next internal client (or end of client list) */ - while (node) { - if (jack_client_is_internal ((jack_client_internal_t*) - node->data)) { + while (client) { + if (jack_client_is_internal (client)) { break; } - node = jack_slist_next (node); + client = client->execlist_next; } - - return node; + return client; } #endif /* JACK_USE_MACH_THREADS */ + +/* Modified to use the new double-linked list of clients in execution order. + * FA 08/2015 + */ static int jack_engine_process (jack_engine_t *engine, jack_nframes_t nframes) { /* precondition: caller has graph_lock */ jack_client_internal_t *client; - JSList *node; engine->process_errors = 0; - for (node = engine->clients; node; node = jack_slist_next (node)) { - jack_client_control_t *ctl = - ((jack_client_internal_t*)node->data)->control; + for (client = engine->execlist_head; client; client = client->execlist_next) { + jack_client_control_t *ctl = client->control; ctl->state = NotTriggered; ctl->timed_out = 0; ctl->awake_at = 0; ctl->finished_at = 0; } - for (node = engine->clients; engine->process_errors == 0 && node; ) { - - client = (jack_client_internal_t*)node->data; - DEBUG ("considering client %s for processing", - client->control->name); + client = engine->execlist_head; + while (client) { + DEBUG ("considering client %s for processing", client->control->name); if (!client->control->active || (!client->control->process_cbset && !client->control->thread_cb_cbset) || client->control->dead) { - node = jack_slist_next (node); + client = client->execlist_next; } else if (jack_client_is_internal (client)) { - node = jack_process_internal (engine, node, nframes); + client = jack_process_internal (engine, client, nframes); } else { - node = jack_process_external (engine, node); + client = jack_process_external (engine, client); } } return engine->process_errors > 0; } + static void jack_calc_cpu_load (jack_engine_t *engine) { @@ -1847,6 +1831,13 @@ jack_engine_new (int realtime, int rtpriority, int do_mlock, int do_unlock, engine->clients = 0; engine->reserved_client_names = 0; + /* Added new double-linked list of clients in execution order. + * FA 08/2015 + */ + engine->execlist_head = 0; + engine->execlist_tail = 0; + engine->execlist_request = 0; + engine->pfd_size = 0; engine->pfd_max = 0; engine->pfd = 0; @@ -3179,159 +3170,89 @@ again: return status; } + +/* Modified to use the new double-linked list of clients in execution order + * FA 08/2015 + */ int jack_rechain_graph (jack_engine_t *engine) { - JSList *node, *next; - unsigned long n; - int err = 0; - jack_client_internal_t *subgraph_client, *next_client; + jack_client_internal_t *client, *subgraph_client; jack_event_t event; int upstream_is_jackd; + unsigned long n; + + VERBOSE (engine, "++ jack_rechain_graph():"); VALGRIND_MEMSET (&event, 0, sizeof(event)); jack_clear_fifos (engine); - subgraph_client = 0; - VERBOSE (engine, "++ jack_rechain_graph():"); - event.type = GraphReordered; - for (n = 0, node = engine->clients, next = NULL; node; node = next) { - - jack_client_internal_t* client = (jack_client_internal_t*)node->data; - - next = jack_slist_next (node); - - if (!client->control->process_cbset && !client->control->thread_cb_cbset) { + n = 0; + for (client = engine->execlist_head; client; client = client->execlist_next) { + /* We are only interested in active clients that have a process callback */ + if (!client->control->active || + (!client->control->process_cbset && !client->control->thread_cb_cbset)) { continue; } - VERBOSE (engine, "+++ client is now %s active ? %d", - client->control->name, client->control->active); - - if (client->control->active) { - - /* find the next active client. its ok for - * this to be NULL */ - - while (next) { - if (client->control->active && (client->control->process_cbset || client->control->thread_cb_cbset)) { - break; - } - next = jack_slist_next (next); - } - ; - - if (next == NULL) { - next_client = NULL; - } else { - next_client = (jack_client_internal_t*) - next->data; - } + VERBOSE (engine, "+++ client is now %s", client->control->name); client->execution_order = n; - client->next_client = next_client; if (jack_client_is_internal (client)) { - - /* break the chain for the current - * subgraph. the server will wait for - * chain on the nth FIFO, and will - * then execute this internal - * client. */ - + /* Break the chain for the current subgraph. The server will + * wait for the chain on the nth FIFO, and will then execute + * this internal client. */ if (subgraph_client) { - subgraph_client->subgraph_wait_fd = - jack_get_fifo_fd (engine, n); - VERBOSE (engine, "client %s: wait_fd=" - "%d, execution_order=" - "%lu.", - subgraph_client-> - control->name, - subgraph_client-> - subgraph_wait_fd, n); + subgraph_client->subgraph_wait_fd = jack_get_fifo_fd (engine, n); + VERBOSE (engine, "client %s: wait_fd=%d execution_order=%lu.", + subgraph_client->control->name, + subgraph_client->subgraph_wait_fd, n); n++; } - - VERBOSE (engine, "client %s: internal " - "client, execution_order=" - "%lu.", + VERBOSE (engine, "client %s: internal client, execution_order=%lu.", client->control->name, n); - /* this does the right thing for - * internal clients too - */ - + /* this does the right thing for internal clients too */ jack_deliver_event (engine, client, &event); - subgraph_client = 0; - } else { - - if (subgraph_client == NULL) { - - /* start a new subgraph. the - * engine will start the chain - * by writing to the nth - * FIFO. - */ - - subgraph_client = client; - subgraph_client->subgraph_start_fd = - jack_get_fifo_fd (engine, n); - VERBOSE (engine, "client %s: " - "start_fd=%d, execution" - "_order=%lu.", - subgraph_client-> - control->name, - subgraph_client-> - subgraph_start_fd, n); - - /* this external client after - this will have jackd as its - upstream connection. - */ - - upstream_is_jackd = 1; - - } else { - VERBOSE (engine, "client %s: in" - " subgraph after %s, " - "execution_order=" - "%lu.", - client->control->name, - subgraph_client-> - control->name, n); + if (subgraph_client) { + /* continue current subgraph. */ subgraph_client->subgraph_wait_fd = -1; - - /* this external client after - this will have another - client as its upstream - connection. - */ - upstream_is_jackd = 0; + VERBOSE (engine, "client %s: in subgraph after %s, " + "execution_order=%lu.", + client->control->name, + subgraph_client->control->name, n); + } else { + /* start a new subgraph. The engine will start the chain + * by writing to the nth FIFO. */ + subgraph_client = client; + subgraph_client->subgraph_start_fd = jack_get_fifo_fd (engine, n); + upstream_is_jackd = 1; + VERBOSE (engine, "client %s: start_fd=%d " + "execution_order=%lu.", + subgraph_client->control->name, + subgraph_client->subgraph_start_fd, n); } - /* make sure fifo for 'n + 1' exists - * before issuing client reorder - */ - (void)jack_get_fifo_fd ( - engine, client->execution_order + 1); + /* make sure fifo for 'n + 1' exists before issuing client reorder. */ + (void)jack_get_fifo_fd (engine, client->execution_order + 1); event.x.n = client->execution_order; event.y.n = upstream_is_jackd; jack_deliver_event (engine, client, &event); n++; } } - } if (subgraph_client) { - subgraph_client->subgraph_wait_fd = - jack_get_fifo_fd (engine, n); + /* finish current subgraph. */ + subgraph_client->subgraph_wait_fd = jack_get_fifo_fd (engine, n); VERBOSE (engine, "client %s: wait_fd=%d, " "execution_order=%lu (last client).", subgraph_client->control->name, @@ -3339,10 +3260,10 @@ jack_rechain_graph (jack_engine_t *engine) } VERBOSE (engine, "-- jack_rechain_graph()"); - - return err; + return 0; } + static jack_nframes_t jack_get_port_total_latency (jack_engine_t *engine, jack_port_internal_t *port, int hop_count, @@ -3475,13 +3396,11 @@ jack_compute_all_port_total_latencies (jack_engine_t *engine) for (i = 0; i < engine->control->port_max; i++) { if (shared[i].in_use) { - if (shared[i].flags & JackPortIsOutput) { toward_port = FALSE; } else { toward_port = TRUE; } - shared[i].total_latency = jack_get_port_total_latency ( engine, &engine->internal_ports[i], @@ -3490,238 +3409,328 @@ jack_compute_all_port_total_latencies (jack_engine_t *engine) } } + +/* Modified to use the new double-linked list of clients in execution order + * FA 08/2015 + */ static void jack_compute_new_latency (jack_engine_t *engine) { - JSList *node; - JSList *reverse_list = NULL; + jack_client_internal_t* client; jack_event_t event; VALGRIND_MEMSET (&event, 0, sizeof(event)); - event.type = LatencyCallback; - event.x.n = 0; - /* iterate over all clients in graph order, and emit - * capture latency callback. - * also builds up list in reverse graph order. - */ - for (node = engine->clients; node; node = jack_slist_next (node)) { - - jack_client_internal_t* client = (jack_client_internal_t*)node->data; - reverse_list = jack_slist_prepend (reverse_list, client); + /* iterate over all clients in graph order and emit capture latency callback. */ + event.x.n = 0; + for (client = engine->execlist_head; client; client = client->execlist_next) jack_deliver_event (engine, client, &event); - } - if (engine->driver) { jack_deliver_event (engine, engine->driver->internal_client, &event); } - /* now issue playback latency callbacks in reverse graphorder - */ + /* iterate over all clients in reverse order and emit playback latency callback. */ event.x.n = 1; - for (node = reverse_list; node; node = jack_slist_next (node)) { - jack_client_internal_t* client = (jack_client_internal_t*)node->data; + for (client = engine->execlist_tail; client; client = client->execlist_prev) jack_deliver_event (engine, client, &event); - } - if (engine->driver) { jack_deliver_event (engine, engine->driver->internal_client, &event); } - - jack_slist_free (reverse_list); } -/* How the sort works: +/* HOW THE SORT WORKS (new version) + * FA 08/2015 + * + * Each client has a linked list called 'feedlist' of other clients + * directly depending on data from it. Element of the list are structs + * of type jack_feedcounts_t, which contains: + * + * .next : pointer to the next list element (this is not a JSlist). + * .client : pointer to the destination client depending on this one. + * .fwd_count : counter of forward direct connections. + * .rev_count : counter of reversed direct connections. + * + * The counters are incremented or decremented by port connect or disconnect + * requests. At least one of them will be non-zero. Reversed dependencies + * are those created by connections that create a cycle. * - * Each client has a "sortfeeds" list of clients indicating which clients - * it should be considered as feeding for the purposes of sorting the - * graph. This list differs from the clients it /actually/ feeds in the - * following ways: + * As in the previous version, connections having the driver as destination + * are ignored in order to avoid apparent cycles. Note: things just work + * this way so this was not changed, but it would be cleaner to represent + * the driver as two clients. This would allow to represent all dependencies + * (which could be interesting for debugging) without creating fake cycles. * - * 1. Connections from a client to itself are disregarded + * In contrast to the previous version, there is no implicit dependency + * of each client on the driver, this is created only when there is an + * actual connection from the capture part. Other logic ensures that the + * driver will run first anyway, even if no clients are connected to it + * at all. * - * 2. Connections to a driver client are disregarded + * Each client also has a counter 'depcount' indicating from how many + * other clients it requires input. This is always equal to the number + * of other clients having a feedlist element referring to this one. * - * 3. If a connection from A to B is a feedback connection (ie there was - * already a path from B to A when the connection was made) then instead - * of B appearing on A's sortfeeds list, A will appear on B's sortfeeds - * list. + * The algorithm that actually determines the execution order is run only + * when an element is added or revomed from any of the 'feedlist' lists, + * and when clients are activated or deactivated. It consists of two + * parts, each of them controlled by a binary flag maintained in + * engine->execlist_requestm and, if necessary, set by the port connect + * and disconnect routines. * - * If client A is on client B's sortfeeds list, client A must come after - * client B in the execution order. The above 3 rules ensure that the - * sortfeeds relation is always acyclic so that all ordering constraints - * can actually be met. + * First, if the EXECLIST_CYCLE flag is set, we test if any feedback + * connections can be reverted to normal ones. If there are any, we + * also set the EXECLIST_ORDER flag in case it was not already set. + * Second, if the EXECLIST_ORDER flag is set, a double linked list + * of clients in execution order is constructed. This list is then + * used by all other routines that control running a cycle. * - * Each client also has a "truefeeds" list which is the same as sortfeeds - * except that feedback connections appear normally instead of reversed. - * This is used to detect whether the graph has become acyclic. + * The 'sorting' works as follows. Each client has a 'depcheck' field + * which is originally set to its 'depcount' value. The list is reset, + * and the driver is added to it. Then we loop over the engine->clients + * JSlist, and add any clients which have their 'depcheck' equal to + * zero to the new list. * + * Whenever a client is added to the new list, its 'depcheck' is set to + * -1 so that client won't be considered again, and we loop over its + * 'feedlist' and decrement the 'depcheck' of each client on it. When + * this decrements to zero, that client is added to the list recursively. + * + * The complexity of this algorithm is linear in the number of clients + * plus the number of direct dependencies between them (i.e. the sum + * of the lenghts of all feedlists). Multiple port connections between + * the same clients count as a single dependency. + * + * Finally, the new double linked list is copied to the original + * engine->clients JSlist. This ensures that in this list the driver + * will always come before any active clients. It's not clear if this + * is actually necessary, as all code related to running a cycle is + * using the new list anyway (as far as I could verify), and all + * other uses seem to be related to looking up client data only. + * + * Another open question is if jack_rechain_graph() needs to be + * called if the order didn't change. If not, the call could be + * moved to the place indicated in jack_check_execorder(). */ -void -jack_sort_graph (jack_engine_t *engine) -{ - /* called, obviously, must hold engine->client_lock */ - - VERBOSE (engine, "++ jack_sort_graph"); - engine->clients = jack_slist_sort (engine->clients, - (JCompareFunc)jack_client_sort); - jack_compute_all_port_total_latencies (engine); - jack_compute_new_latency (engine); - jack_rechain_graph (engine); - engine->timeout_count = 0; - VERBOSE (engine, "-- jack_sort_graph"); -} -static int -jack_client_sort (jack_client_internal_t *a, jack_client_internal_t *b) +/* Test if there is a direct dependency of client B on client A. + * This may be because A feeds B directly, or because there is + * a feedback connection from B to A. + * Returns the jack_feedcounts_t for the dependency if it exists, + * zero otherwise. + */ +static jack_feedcounts_t * +depends_direct (jack_client_internal_t *A, jack_client_internal_t *B) { - /* drivers are forced to the front, ie considered as sources - rather than sinks for purposes of the sort */ + jack_feedcounts_t *F; - if (jack_client_feeds_transitive (a, b) || - (a->control->type == ClientDriver && - b->control->type != ClientDriver)) { - return -1; - } else if (jack_client_feeds_transitive (b, a) || - (b->control->type == ClientDriver && - a->control->type != ClientDriver)) { - return 1; - } else { - return 0; + for (F = A->feedlist; F; F = F->next) + if (F->client == B) { + return F; } + return 0; } -/* transitive closure of the relation expressed by the sortfeeds lists. */ +/* Test if client B depends on output from client A, either directly + * or via other clients. Feedback connections are not considered in + * this case. This is used to detect cycles. + * Returns 1 if the dependency exists, 0 otherwise. + */ static int -jack_client_feeds_transitive (jack_client_internal_t *source, - jack_client_internal_t *dest ) +depends_closure (jack_client_internal_t *A, jack_client_internal_t *B) { - jack_client_internal_t *med; - JSList *node; + jack_feedcounts_t *F; - if (jack_slist_find (source->sortfeeds, dest)) { - return 1; + for (F = A->feedlist; F; F = F->next) { + if (F->fwd_count == 0) { + continue; } - - for (node = source->sortfeeds; node; node = jack_slist_next (node)) { - - med = (jack_client_internal_t*)node->data; - - if (jack_client_feeds_transitive (med, dest)) { + if (F->client == B) { return 1; + } else { return depends_closure (F->client, B); } } - } - return 0; } -/** - * Checks whether the graph has become acyclic and if so modifies client - * sortfeeds lists to turn leftover feedback connections into normal ones. - * This lowers latency, but at the expense of some data corruption. +/* Create a new direct dependency of client B on client A. */ static void -jack_check_acyclic (jack_engine_t *engine) +add_depends (jack_client_internal_t *A, jack_client_internal_t *B, int fwd, int rev) { - JSList *srcnode, *dstnode, *portnode, *connnode; - jack_client_internal_t *src, *dst; - jack_port_internal_t *port; - jack_connection_internal_t *conn; - int stuck; - int unsortedclients = 0; - - VERBOSE (engine, "checking for graph become acyclic"); + jack_feedcounts_t *F; - for (srcnode = engine->clients; srcnode; - srcnode = jack_slist_next (srcnode)) { - - src = (jack_client_internal_t*)srcnode->data; - src->tfedcount = src->fedcount; - unsortedclients++; + F = (jack_feedcounts_t*)malloc (sizeof(jack_feedcounts_t)); + F->client = B; + F->fwd_count = fwd; + F->rev_count = rev; + F->next = A->feedlist; + A->feedlist = F; + B->depcount++; } - stuck = FALSE; - - /* find out whether a normal sort would have been possible */ - while (unsortedclients && !stuck) { - - stuck = TRUE; - - for (srcnode = engine->clients; srcnode; - srcnode = jack_slist_next (srcnode)) { - - src = (jack_client_internal_t*)srcnode->data; - - if (!src->tfedcount) { - - stuck = FALSE; - unsortedclients--; - src->tfedcount = -1; +/* Delete a direct dependency of client B on client A. + */ +static void +rem_depends (jack_client_internal_t *A, jack_client_internal_t *B) +{ + jack_feedcounts_t *X, *Y; + + for (X = 0, Y = A->feedlist; Y; X = Y, Y = Y->next) { + if (Y->client == B) { + if (X) { + X->next = Y->next; + } else { A->feedlist = Y->next; } + B->depcount--; + free (Y); + return; + } + } +} - for (dstnode = src->truefeeds; dstnode; - dstnode = jack_slist_next (dstnode)) { - dst = (jack_client_internal_t*) - dstnode->data; - dst->tfedcount--; +/* Add client C at the tail of the execution order list, and then + * recursively test if any others can be run after C has finished. + */ +static void +append_execlist (jack_engine_t *engine, jack_client_internal_t *C) +{ + jack_client_internal_t *D; + jack_feedcounts_t *F; + + /* This client must not be considered again. */ + C->depcheck = -1; + /* Append at end of list */ + C->execlist_next = 0; + if (engine->execlist_tail) { + C->execlist_prev = engine->execlist_tail; + engine->execlist_tail->execlist_next = C; + } else { + C->execlist_prev = 0; + engine->execlist_head = C; } + engine->execlist_tail = C; + /* Check if any others can be run after this one. */ + for (F = C->feedlist; F; F = F->next) { + D = F->client; + if (--(D->depcheck) == 0) { + append_execlist (engine, D); } } } - if (stuck) { - - VERBOSE (engine, "graph is still cyclic" ); - } else { - - VERBOSE (engine, "graph has become acyclic"); - - /* turn feedback connections around in sortfeeds */ - for (srcnode = engine->clients; srcnode; - srcnode = jack_slist_next (srcnode)) { - - src = (jack_client_internal_t*)srcnode->data; - - for (portnode = src->ports; portnode; - portnode = jack_slist_next (portnode)) { - - port = (jack_port_internal_t*)portnode->data; - - for (connnode = port->connections; connnode; - connnode = jack_slist_next (connnode)) { - - conn = (jack_connection_internal_t*) - connnode->data; - - if (conn->dir == -1 ) { - /*&& - conn->srcclient == src) */ - - VERBOSE (engine, - "reversing connection from " - "%s to %s", - conn->srcclient->control->name, - conn->dstclient->control->name); - conn->dir = 1; - conn->dstclient->sortfeeds = - jack_slist_remove - (conn->dstclient->sortfeeds, - conn->srcclient); - conn->srcclient->sortfeeds = - jack_slist_prepend - (conn->srcclient->sortfeeds, - conn->dstclient ); +/* Recreate the execution order client list if necessary. + */ +void +jack_check_execorder (jack_engine_t *engine) +{ + jack_client_internal_t *C, *D; + jack_feedcounts_t *F; + JSList *N; + + if (engine->execlist_request & EXECLIST_CYCLE) { + /* Test if any feedback connections can be reverted + * to normal */ + for (C = engine->execlist_head; C; C = C->execlist_next) { + for (F = C->feedlist; F; F = F->next) { + int rev; + D = F->client; + if (F->rev_count == 0) { + continue; } + if (depends_closure (C, D)) { + continue; } + /* printf ("Reverting feedback %s -> %s\n", D->control->name, C->control->name); */ + rev = F->rev_count; + rem_depends (C, D); + add_depends (D, C, rev, 0); + engine->execlist_request |= EXECLIST_ORDER; } } - engine->feedbackcount = 0; + } +/* + puts ("---------------------------------------------"); + printf ("Original client list:\n"); + for (N = engine->clients; N; N = jack_slist_next (N)) + { + C = (jack_client_internal_t *) N->data; + printf ("%-20s %d\n", C->control->name, C->depcount); + for (F = C->feedlist; F; F = F->next) + { + printf (" %-20s %3d %3d\n", F->client->control->name, F->fwd_count, F->rev_count); } } + puts ("---------------------------------------------"); + */ + if (engine->execlist_request & EXECLIST_ORDER) { +/* + puts ("Re-ordering..."); + */ + /* Put clients in correct execution order depending + on their dependencies. */ + D = 0; + for (N = engine->clients; N; N = jack_slist_next (N)) { + C = (jack_client_internal_t*)N->data; + C->depcheck = C->depcount; + if (C->control->type == ClientDriver) { + D = C; + } + } + /* Reset bidirectional linked list and build it again. */ + engine->execlist_head = 0; + engine->execlist_tail = 0; + /* The driver must be first even if nobody is using capture data. */ + if (D) { + append_execlist (engine, D); + } + /* Then loop over all the other clients. */ + for (N = engine->clients; N; N = jack_slist_next (N)) { + C = (jack_client_internal_t*)N->data; + if (C->depcheck == 0) { + append_execlist (engine, C); + } + } + + /* Copy execlist to engine->clients. Just overwrite the data pointers. */ + for (C = engine->execlist_head, N = engine->clients; + C && N; + C = C->execlist_next, N = N->next) + N->data = C; + + /* ??? Maybe call jack_rechain_graph here instead of in jack_sort_graph() ??? */ + } +/* + puts ("---------------------------------------------"); + printf ("Ordered client list:\n"); + for (C = engine->execlist_head; C; C = C->execlist_next) + { + printf (" %-20s\n", C->control->name); + } + puts ("---------------------------------------------"); + */ + engine->execlist_request = 0; +} + + +/* Modified for new client execution order algorithm. + * FA 08/2015 + */ +void +jack_sort_graph (jack_engine_t *engine) +{ + /* caller, obviously, must hold engine->client_lock */ + VERBOSE (engine, "++ jack_sort_graph"); + jack_check_execorder (engine); + jack_compute_all_port_total_latencies (engine); + jack_compute_new_latency (engine); + jack_rechain_graph (engine); + engine->timeout_count = 0; + VERBOSE (engine, "-- jack_sort_graph"); +} + /** * Dumps current engine configuration. @@ -3788,6 +3797,11 @@ void jack_dump_configuration (jack_engine_t *engine, int take_lock) jack_info ("engine.c: <-- dump ends -->"); } + + +/* Modified for new client execution order algorithm. + * FA 08/2015 + */ static int jack_port_do_connect (jack_engine_t *engine, const char *source_port, @@ -3867,9 +3881,16 @@ jack_port_do_connect (jack_engine_t *engine, } } + if (dstport->connections && !dstport->shared->has_mixdown) { + jack_port_type_info_t *port_type = + jack_port_type_info (engine, dstport); + jack_error ("cannot make multiple connections to a port of" + " type [%s]", port_type->type_name); + return -1; + } + connection = (jack_connection_internal_t*) malloc (sizeof(jack_connection_internal_t)); - connection->source = srcport; connection->destination = dstport; connection->srcclient = srcclient; @@ -3877,85 +3898,68 @@ jack_port_do_connect (jack_engine_t *engine, src_id = srcport->shared->id; dst_id = dstport->shared->id; - jack_lock_graph (engine); - if (dstport->connections && !dstport->shared->has_mixdown) { - jack_port_type_info_t *port_type = - jack_port_type_info (engine, dstport); - jack_error ("cannot make multiple connections to a port of" - " type [%s]", port_type->type_name); - free (connection); - jack_unlock_graph (engine); - return -1; - } else { - if (dstclient->control->type == ClientDriver) { /* Ignore output connections to drivers for purposes of sorting. Drivers are executed first in the sort order anyway, and we don't want to treat graphs such as driver -> client -> driver as containing feedback */ - VERBOSE (engine, "connect %s and %s (output)", srcport->shared->name, dstport->shared->name); - - connection->dir = 1; - } else if (srcclient != dstclient) { + jack_feedcounts_t *F; + int dir = 0; + + if ((F = depends_direct (srcclient, dstclient)) != 0) { + /* We already have a direct dependency, + just increment the connection count. */ + F->fwd_count++; + dir = 1; + } else if ((F = depends_direct (dstclient, srcclient)) != 0) { + /* We already have a reverse direct dependency, + just increment the reverse connection count. */ + F->rev_count++; + dir = -1; + } else if (depends_closure (dstclient, srcclient)) { + /* We have an inverse dependency, this + connection must be a delayed one. */ + add_depends (dstclient, srcclient, 0, 1); + engine->execlist_request = EXECLIST_ORDER; + dir = -1; + } else { + /* First normal connection from source + to destination client. */ + add_depends (srcclient, dstclient, 1, 0); + engine->execlist_request = EXECLIST_ORDER; + dir = 1; + } - srcclient->truefeeds = jack_slist_prepend - (srcclient->truefeeds, dstclient); - - dstclient->fedcount++; - - if (jack_client_feeds_transitive (dstclient, - srcclient ) || - (dstclient->control->type == ClientDriver && - srcclient->control->type != ClientDriver)) { - - /* dest is running before source so - this is a feedback connection */ - + if (dir > 0) { + VERBOSE (engine, + "connect %s and %s (forward)", + srcport->shared->name, + dstport->shared->name); + } else { VERBOSE (engine, "connect %s and %s (feedback)", srcport->shared->name, dstport->shared->name); - - dstclient->sortfeeds = jack_slist_prepend - (dstclient->sortfeeds, srcclient); - - connection->dir = -1; engine->feedbackcount++; VERBOSE (engine, "feedback count up to %d", engine->feedbackcount); - - } else { - - /* this is not a feedback connection */ - - VERBOSE (engine, - "connect %s and %s (forward)", - srcport->shared->name, - dstport->shared->name); - - srcclient->sortfeeds = jack_slist_prepend - (srcclient->sortfeeds, dstclient); - - connection->dir = 1; } } else { /* this is a connection to self */ - VERBOSE (engine, "connect %s and %s (self)", srcport->shared->name, dstport->shared->name); - connection->dir = 0; } dstport->connections = @@ -3963,29 +3967,28 @@ jack_port_do_connect (jack_engine_t *engine, srcport->connections = jack_slist_prepend (srcport->connections, connection); - DEBUG ("actually sorted the graph..."); - jack_send_connection_notification (engine, srcport->shared->client_id, src_id, dst_id, TRUE); - - jack_send_connection_notification (engine, dstport->shared->client_id, dst_id, src_id, TRUE); - /* send a port connection notification just once to everyone who cares excluding clients involved in the connection */ - - jack_notify_all_port_interested_clients (engine, srcport->shared->client_id, dstport->shared->client_id, src_id, dst_id, 1); - + /* send a port connection notification just once to everyone who + * cares excluding clients involved in the connection */ + jack_notify_all_port_interested_clients (engine, + srcport->shared->client_id, + dstport->shared->client_id, + src_id, dst_id, 1); jack_sort_graph (engine); - } - jack_unlock_graph (engine); - return 0; } + +/* Modified for new client execution order algorithm. + * FA 08/2015 + */ int jack_port_disconnect_internal (jack_engine_t *engine, jack_port_internal_t *srcport, @@ -3994,19 +3997,15 @@ jack_port_disconnect_internal (jack_engine_t *engine, { JSList *node; jack_connection_internal_t *connect; - int ret = -1; + jack_client_internal_t *src, *dst; jack_port_id_t src_id, dst_id; - int check_acyclic = engine->feedbackcount; + int ret = -1; /* call tree **** MUST HOLD **** engine->client_lock. */ - for (node = srcport->connections; node; - node = jack_slist_next (node)) { - + for (node = srcport->connections; node; node = jack_slist_next (node)) { connect = (jack_connection_internal_t*)node->data; - if (connect->source == srcport && - connect->destination == dstport) { - + if (connect->source == srcport && connect->destination == dstport) { VERBOSE (engine, "DIS-connect %s and %s", srcport->shared->name, dstport->shared->name); @@ -4017,7 +4016,7 @@ jack_port_disconnect_internal (jack_engine_t *engine, dstport->connections = jack_slist_remove (dstport->connections, connect); - + free (connect); src_id = srcport->shared->id; dst_id = dstport->shared->id; @@ -4040,59 +4039,60 @@ jack_port_disconnect_internal (jack_engine_t *engine, engine, dstport->shared->client_id, dst_id, src_id, FALSE); - /* send a port connection notification just once to everyone who cares excluding clients involved in the connection */ - - jack_notify_all_port_interested_clients (engine, srcport->shared->client_id, dstport->shared->client_id, src_id, dst_id, 0); - - if (connect->dir) { - - jack_client_internal_t *src; - jack_client_internal_t *dst; - - src = jack_client_internal_by_id - (engine, srcport->shared->client_id); - - dst = jack_client_internal_by_id - (engine, dstport->shared->client_id); - - src->truefeeds = jack_slist_remove - (src->truefeeds, dst); - - dst->fedcount--; - - if (connect->dir == 1) { - /* normal connection: remove dest from - source's sortfeeds list */ - src->sortfeeds = jack_slist_remove - (src->sortfeeds, dst); - } else { - /* feedback connection: remove source - from dest's sortfeeds list */ - dst->sortfeeds = jack_slist_remove - (dst->sortfeeds, src); + /* send a port connection notification just once to everyone + who cares excluding clients involved in the connection */ + jack_notify_all_port_interested_clients (engine, + srcport->shared->client_id, + dstport->shared->client_id, + src_id, dst_id, 0); + + /* Update client dependencies */ + src = jack_client_internal_by_id (engine, srcport->shared->client_id); + dst = jack_client_internal_by_id (engine, dstport->shared->client_id); + /* Again we ignore connections if the destination is the driver. */ + if ((src != dst) && (dst->control->type != ClientDriver)) { + jack_feedcounts_t *F; + if ((F = depends_direct (src, dst)) != 0) { + if (F->fwd_count > 0) { + F->fwd_count--; + } + if (F->fwd_count == 0) { + /* A direct forward dependency no longer exists. + This could break a cycle, so we'll test. */ + engine->execlist_request = EXECLIST_CYCLE; + if (F->rev_count == 0) { + /* A direct dependency no longer exists, + remove the corresponding list element. */ + rem_depends (src, dst); + engine->execlist_request |= EXECLIST_ORDER; + } + } + } else if ((F = depends_direct (dst, src)) != 0) { + /* Removing a feedback connection. */ + if (F->rev_count > 0) { + F->rev_count--; + } + if ((F->fwd_count | F->rev_count) == 0) { + /* A direct dependency no longer exists, + remove the corresponding list element. */ + rem_depends (dst, src); + engine->execlist_request = EXECLIST_ORDER; + } engine->feedbackcount--; VERBOSE (engine, "feedback count down to %d", engine->feedbackcount); } - } /* else self-connection: do nothing */ - - free (connect); - ret = 0; - break; } + jack_sort_graph (engine); + return 0; } - - if (check_acyclic) { - jack_check_acyclic (engine); } - - jack_sort_graph (engine); - - return ret; + return -1; } + static int jack_port_do_disconnect_all (jack_engine_t *engine, jack_port_id_t port_id) |