summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2016-03-02 17:50:14 -0500
committerPaul Davis <paul@linuxaudiosystems.com>2016-03-02 17:50:14 -0500
commitea78c7e06e768a02d6129c43c51473a7f94cfd73 (patch)
tree4952bb3c8cdc309348d4244894770535826ebfb5
parent79c1cd035164c637e6056406d341713f3424fee0 (diff)
downloadjack1-ea78c7e06e768a02d6129c43c51473a7f94cfd73.tar.gz
Revert "use topological sort for client ordering (fons adriaensen)"
This reverts commit 423931219dd3e3b669fde97786cadae92c066dc1.
-rw-r--r--include/engine.h7
-rw-r--r--include/internal.h28
-rw-r--r--jackd/clientengine.c40
-rw-r--r--jackd/engine.c922
4 files changed, 483 insertions, 514 deletions
diff --git a/include/engine.h b/include/engine.h
index 3e869af..5106713 100644
--- a/include/engine.h
+++ b/include/engine.h
@@ -144,13 +144,6 @@ 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 7f9d062..5a14237 100644
--- a/include/internal.h
+++ b/include/internal.h
@@ -452,18 +452,6 @@ 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.
*/
@@ -476,18 +464,14 @@ typedef struct _jack_client_internal {
int event_fd;
int subgraph_start_fd;
int subgraph_wait_fd;
-
- /* 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;
-
+ 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;
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 8f47894..365e287 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,7 +88,6 @@ jack_client_do_deactivate (jack_engine_t *engine,
}
if (sort_graph) {
- engine->execlist_request = EXECLIST_ORDER | EXECLIST_CYCLE;
jack_sort_graph (engine);
}
return 0;
@@ -163,6 +162,7 @@ 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,9 +337,6 @@ 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)
{
@@ -407,7 +404,6 @@ 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);
}
@@ -568,9 +564,6 @@ 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)
{
@@ -582,11 +575,10 @@ 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->feedlist = 0;
- client->depcount = 0;
- client->depcheck = 0;
- client->execlist_next = 0;
+ client->truefeeds = 0;
+ client->sortfeeds = 0;
client->execution_order = UINT_MAX;
+ client->next_client = NULL;
client->handle = NULL;
client->finish = NULL;
client->error = 0;
@@ -772,15 +764,19 @@ 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!",
@@ -796,7 +792,8 @@ setup_client (jack_engine_t *engine, ClientType type, char *name,
pthread_mutex_lock (&engine->request_lock);
}
- } else {
+ } else { /* external client */
+
jack_unlock_graph (engine);
}
@@ -867,7 +864,6 @@ 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)
{
@@ -977,10 +973,6 @@ 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)
{
@@ -1005,8 +997,8 @@ jack_client_activate (jack_engine_t *engine, jack_uuid_t id)
* this point.
*/
- jack_get_fifo_fd (engine, ++engine->external_client_cnt);
- engine->execlist_request = EXECLIST_ORDER;
+ jack_get_fifo_fd (engine,
+ ++engine->external_client_cnt);
jack_sort_graph (engine);
diff --git a/jackd/engine.c b/jackd/engine.c
index 335dc81..c6b2e60 100644
--- a/jackd/engine.c
+++ b/jackd/engine.c
@@ -71,6 +71,7 @@ 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;
@@ -126,6 +127,11 @@ 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);
@@ -566,20 +572,21 @@ jack_set_buffer_size_request (jack_engine_t *engine, jack_nframes_t nframes)
}
-/* 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,
+static JSList *
+jack_process_internal (jack_engine_t *engine, JSList *node,
jack_nframes_t nframes)
{
+ jack_client_internal_t *client;
jack_client_control_t *ctl;
+ client = (jack_client_internal_t*)node->data;
ctl = client->control;
- ctl->state = Running;
- engine->current_client = client;
+
+ /* internal client */
DEBUG ("invoking an internal client's (%s) callbacks", ctl->name);
+ ctl->state = Running;
+ engine->current_client = client;
/* XXX how to time out an internal client? */
@@ -601,11 +608,12 @@ jack_process_internal (jack_engine_t *engine, jack_client_internal_t *client,
ctl->state = Finished;
if (engine->process_errors) {
- return NULL;
- } else { return client->execlist_next; }
+ return NULL; /* will stop the loop */
+ } else {
+ return jack_slist_next (node);
+ }
}
-
#ifdef __linux
/* Linux kernels somewhere between 2.6.18 and 2.6.24 had a bug
@@ -642,22 +650,18 @@ linux_poll_bug_encountered (jack_engine_t* engine, jack_time_t then, jack_time_t
}
#endif
-
#ifdef JACK_USE_MACH_THREADS
-
-/* 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)
+static JSList *
+jack_process_external (jack_engine_t *engine, JSList *node)
{
+ jack_client_internal_t * client = (jack_client_internal_t*)node->data;
jack_client_control_t *ctl;
- engine->current_client = client;
+ client = (jack_client_internal_t*)node->data;
ctl = client->control;
+
+ engine->current_client = client;
+
// a race exists if we do this after the write(2)
ctl->state = Triggered;
ctl->signalled_at = jack_get_microseconds ();
@@ -667,32 +671,35 @@ jack_process_external (jack_engine_t *engine, jack_client_internal_t *client)
ctl->state = Finished;
}
- return client->execlist_next;
+ return jack_slist_next (node);
}
-
#else /* !JACK_USE_MACH_THREADS */
-
-/* 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)
+static JSList *
+jack_process_external (jack_engine_t *engine, JSList *node)
{
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;
- engine->current_client = client;
+ client = (jack_client_internal_t*)node->data;
+
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);
@@ -737,16 +744,22 @@ 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;
}
}
@@ -760,6 +773,7 @@ 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,
@@ -791,6 +805,7 @@ again:
ctl->signalled_at) : 0);
if (jack_check_clients (engine, 1)) {
+
engine->process_errors++;
return NULL; /* will stop the loop */
}
@@ -815,57 +830,58 @@ again:
}
/* Move to next internal client (or end of client list) */
- while (client) {
- if (jack_client_is_internal (client)) {
+ while (node) {
+ if (jack_client_is_internal ((jack_client_internal_t*)
+ node->data)) {
break;
}
- client = client->execlist_next;
+ node = jack_slist_next (node);
}
- return client;
+
+ return node;
}
#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 (client = engine->execlist_head; client; client = client->execlist_next) {
- jack_client_control_t *ctl = client->control;
+ for (node = engine->clients; node; node = jack_slist_next (node)) {
+ jack_client_control_t *ctl =
+ ((jack_client_internal_t*)node->data)->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;
- client = engine->execlist_head;
- while (client) {
- DEBUG ("considering client %s for processing", client->control->name);
+ 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) {
- client = client->execlist_next;
+ node = jack_slist_next (node);
} else if (jack_client_is_internal (client)) {
- client = jack_process_internal (engine, client, nframes);
+ node = jack_process_internal (engine, node, nframes);
} else {
- client = jack_process_external (engine, client);
+ node = jack_process_external (engine, node);
}
}
return engine->process_errors > 0;
}
-
static void
jack_calc_cpu_load (jack_engine_t *engine)
{
@@ -1831,13 +1847,6 @@ 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;
@@ -3170,89 +3179,159 @@ 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)
{
- jack_client_internal_t *client, *subgraph_client;
+ JSList *node, *next;
+ unsigned long n;
+ int err = 0;
+ jack_client_internal_t *subgraph_client, *next_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;
- 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)) {
+ 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) {
continue;
}
- VERBOSE (engine, "+++ client is now %s", client->control->name);
+ 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;
+ }
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 the chain on the nth FIFO, and will then execute
- * this 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. */
+
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) {
- /* continue current subgraph. */
+
+ 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);
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) {
- /* finish current subgraph. */
- subgraph_client->subgraph_wait_fd = jack_get_fifo_fd (engine, n);
+ 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,
@@ -3260,9 +3339,9 @@ jack_rechain_graph (jack_engine_t *engine)
}
VERBOSE (engine, "-- jack_rechain_graph()");
- return 0;
-}
+ return err;
+}
static jack_nframes_t
jack_get_port_total_latency (jack_engine_t *engine,
@@ -3396,11 +3475,13 @@ 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],
@@ -3409,328 +3490,238 @@ 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)
{
- jack_client_internal_t* client;
+ JSList *node;
+ JSList *reverse_list = NULL;
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. */
- event.x.n = 0;
- for (client = engine->execlist_head; client; client = client->execlist_next)
+ /* 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);
jack_deliver_event (engine, client, &event);
+ }
+
if (engine->driver) {
jack_deliver_event (engine, engine->driver->internal_client, &event);
}
- /* iterate over all clients in reverse order and emit playback latency callback. */
+ /* now issue playback latency callbacks in reverse graphorder
+ */
event.x.n = 1;
- for (client = engine->execlist_tail; client; client = client->execlist_prev)
+ for (node = reverse_list; node; node = jack_slist_next (node)) {
+ jack_client_internal_t* client = (jack_client_internal_t*)node->data;
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 (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.
+/* How the sort works:
*
- * 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.
+ * 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:
*
- * 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.
+ * 1. Connections from a client to itself 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.
+ * 2. Connections to a driver client are disregarded
*
- * 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.
+ * 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.
*
- * 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.
+ * 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.
*
- * 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.
+ * 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.
*
- * 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 */
-/* 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)
+ 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)
{
- jack_feedcounts_t *F;
+ /* drivers are forced to the front, ie considered as sources
+ rather than sinks for purposes of the sort */
- for (F = A->feedlist; F; F = F->next)
- if (F->client == B) {
- return 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;
}
- return 0;
}
-/* 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.
- */
+/* transitive closure of the relation expressed by the sortfeeds lists. */
static int
-depends_closure (jack_client_internal_t *A, jack_client_internal_t *B)
+jack_client_feeds_transitive (jack_client_internal_t *source,
+ jack_client_internal_t *dest )
{
- jack_feedcounts_t *F;
+ jack_client_internal_t *med;
+ JSList *node;
- for (F = A->feedlist; F; F = F->next) {
- if (F->fwd_count == 0) {
- continue;
+ if (jack_slist_find (source->sortfeeds, dest)) {
+ return 1;
}
- if (F->client == B) {
+
+ for (node = source->sortfeeds; node; node = jack_slist_next (node)) {
+
+ med = (jack_client_internal_t*)node->data;
+
+ if (jack_client_feeds_transitive (med, dest)) {
return 1;
- } else { return depends_closure (F->client, B); }
}
+ }
+
return 0;
}
-/* Create a new direct dependency of client B on client A.
+/**
+ * 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.
*/
static void
-add_depends (jack_client_internal_t *A, jack_client_internal_t *B, int fwd, int rev)
+jack_check_acyclic (jack_engine_t *engine)
{
- jack_feedcounts_t *F;
+ 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;
- 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++;
- }
+ VERBOSE (engine, "checking for graph become acyclic");
-/* 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 (srcnode = engine->clients; srcnode;
+ srcnode = jack_slist_next (srcnode)) {
+
+ src = (jack_client_internal_t*)srcnode->data;
+ src->tfedcount = src->fedcount;
+ unsortedclients++;
}
-}
+ stuck = FALSE;
-/* 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;
+ /* 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;
+
+ for (dstnode = src->truefeeds; dstnode;
+ dstnode = jack_slist_next (dstnode)) {
+
+ dst = (jack_client_internal_t*)
+ dstnode->data;
+ dst->tfedcount--;
}
- 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) {
-/* 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;
+ 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 );
}
- 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;
}
}
- }
-/*
- 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);
+ engine->feedbackcount = 0;
}
}
- 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.
@@ -3797,11 +3788,6 @@ 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,
@@ -3881,16 +3867,9 @@ 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;
@@ -3898,68 +3877,85 @@ 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;
- }
- if (dir > 0) {
- VERBOSE (engine,
- "connect %s and %s (forward)",
- srcport->shared->name,
- dstport->shared->name);
- } else {
+ 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 */
+
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 =
@@ -3967,28 +3963,29 @@ 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,
@@ -3997,15 +3994,19 @@ jack_port_disconnect_internal (jack_engine_t *engine,
{
JSList *node;
jack_connection_internal_t *connect;
- jack_client_internal_t *src, *dst;
- jack_port_id_t src_id, dst_id;
int ret = -1;
+ jack_port_id_t src_id, dst_id;
+ int check_acyclic = engine->feedbackcount;
/* 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);
@@ -4016,7 +4017,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;
@@ -4039,59 +4040,58 @@ 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);
-
- /* 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;
- }
+ /* 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);
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);
}
- return -1;
-}
+ jack_sort_graph (engine);
+
+ return ret;
+}
static int
jack_port_do_disconnect_all (jack_engine_t *engine,