Here is TAILQ version of the patch, along with the event loop diff(which
randomly crashes the server for me again)
---
Makefile.am | 2 +
cmd-monitor.c | 110 ++++++++++++++++++++++++++++
cmd.c | 1 +
examples/monitor.sh | 120 ++++++++++++++++++++++++++++++
monitor.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++
tmux.c | 3 +
tmux.h | 71 ++++++++++++++++++
7 files changed, 513 insertions(+)
create mode 100644 cmd-monitor.c
create mode 100755 examples/monitor.sh
create mode 100644 monitor.c
diff --git a/Makefile.am b/Makefile.am
index 5caa498..af0b35d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -100,6 +100,7 @@ dist_tmux_SOURCES = \
cmd-load-buffer.c \
cmd-lock-server.c \
cmd-move-window.c \
+ cmd-monitor.c \
cmd-new-session.c \
cmd-new-window.c \
cmd-paste-buffer.c \
@@ -154,6 +155,7 @@ dist_tmux_SOURCES = \
layout.c \
log.c \
mode-key.c \
+ monitor.c \
names.c \
notify.c \
options-table.c \
diff --git a/cmd-monitor.c b/cmd-monitor.c
new file mode 100644
index 0000000..9fcbba7
--- /dev/null
+++ b/cmd-monitor.c
@@ -0,0 +1,110 @@
+/* $Id$ */
+
+/*
+ * Copyright (c) 2013 Thiago de Arruda<tpadilh...@gmail.com>
+ *
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "tmux.h"
+
+enum cmd_retval cmd_monitor_exec(struct cmd *, struct cmd_q *);
+enum cmd_retval monitor_lock(struct monitors *, const char *, struct cmd_q *);
+enum cmd_retval monitor_unlock(struct monitors *, const char *, struct cmd_q *);
+enum cmd_retval monitor_wait(struct monitors *, const char *, struct cmd_q *);
+enum cmd_retval monitor_signal(struct monitors *, const char *, struct cmd_q *);
+
+const struct cmd_entry cmd_monitor_entry = {
+ "monitor", NULL,
+ "luws", 1, 1,
+ "[-l | -u | -w | -s] name",
+ 0,
+ NULL,
+ NULL,
+ cmd_monitor_exec
+};
+
+enum cmd_retval
+cmd_monitor_exec(struct cmd *self, struct cmd_q *cmdq)
+{
+ struct args *args = self->args;
+ const char *name = args->argv[0];
+ struct monitors *head = &global_monitors;
+
+ if (args_has(args, 'l'))
+ return monitor_lock(head, name, cmdq);
+ else if (args_has(args, 'u'))
+ return monitor_unlock(head, name, cmdq);
+ else if (args_has(args, 'w'))
+ return monitor_wait(head, name, cmdq);
+ else if (args_has(args, 's'))
+ return monitor_signal(head, name, cmdq);
+
+ cmdq_error(cmdq, "Must specify an action to be executed on the monitor");
+
+ return (CMD_RETURN_ERROR);
+}
+
+enum cmd_retval
+monitor_lock(struct monitors *head, const char *name, struct cmd_q *cmdq)
+{
+ if (monitor_enter(head, monitor_find_or_create(head, name), cmdq, MONITOR_ENQUEUE))
+ return (CMD_RETURN_NORMAL);
+
+ return (CMD_RETURN_WAIT);
+}
+
+enum cmd_retval
+monitor_unlock(struct monitors *head, const char *name, struct cmd_q *cmdq)
+{
+ struct window_pane *pane;
+ struct monitor_node *m;
+
+ m = monitor_find(head, name);
+ cmd_find_pane(cmdq, NULL, NULL, &pane);
+
+ if (m == NULL || m->pane != pane) {
+ cmdq_error(cmdq, "Not holding the lock to '%s'", name);
+ return (CMD_RETURN_ERROR);
+ }
+
+ m->count--;
+
+ if (m->count == 0)
+ monitor_shift(head, m);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+enum cmd_retval
+monitor_wait(struct monitors *head, const char *name, struct cmd_q *cmdq)
+{
+ monitor_enter(head, monitor_find_or_create(head, name), cmdq, MONITOR_ENQUEUE | MONITOR_WAIT);
+ return (CMD_RETURN_WAIT);
+}
+
+enum cmd_retval
+monitor_signal(struct monitors *head, const char *name, struct cmd_q *cmdq)
+{
+ struct monitor_node *m;
+
+ m = monitor_find(head, name);
+
+ if (m != NULL)
+ if (!monitor_enter(head, m, cmdq, MONITOR_ENQUEUE | MONITOR_SIGNAL))
+ return (CMD_RETURN_WAIT);
+
+ return (CMD_RETURN_NORMAL);
+}
+
diff --git a/cmd.c b/cmd.c
index f3326df..c4386dc 100644
--- a/cmd.c
+++ b/cmd.c
@@ -71,6 +71,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_lock_session_entry,
&cmd_move_pane_entry,
&cmd_move_window_entry,
+ &cmd_monitor_entry,
&cmd_new_session_entry,
&cmd_new_window_entry,
&cmd_next_layout_entry,
diff --git a/examples/monitor.sh b/examples/monitor.sh
new file mode 100755
index 0000000..9157e45
--- /dev/null
+++ b/examples/monitor.sh
@@ -0,0 +1,120 @@
+#!/bin/bash
+
+# Shows how one can make panes synchronize work using the 'monitor' command
+#
+# The monitor object allows four basic operations which are executed
+# with mutual exclusion(meaning the monitor lock needs to be acquired
+# before executing)
+#
+# l: lock the monitor (primitive operation indirectly called by others)
+# u: releases the monitor lock
+# w: enter the monitor 'wait stack' and blocks until a signal is received
+# s: signal all panes waiting in the monitor stack. if the monitor
+# is locked, then it will block
+
+if [ -z $TMUX ]; then
+ echo "Start tmux first" >&2
+ exit 1
+fi
+
+kill_child_panes() {
+ tmux kill-pane -t $pane1
+ tmux kill-pane -t $pane2
+ tmux kill-pane -t $pane3
+ exit
+}
+abspath=$(cd ${0%/*} && echo $PWD/${0##*/})
+
+case $1 in
+ pane1)
+ tmux setw -q @pane1id $TMUX_PANE
+ tmux monitor -s pane1
+ tmux monitor -w pane1
+ tmux split-window -d -h "$abspath pane3"
+ tmux split-window -d -h "$abspath pane2"
+ tmux monitor -w pane1
+ columns=$(tput cols)
+ n=1
+ while true; do
+ for i in $(seq 1 $columns); do
+ sleep "0.1"
+ echo -n $n
+ done
+ echo
+ tmux monitor -s pane2
+ tmux monitor -w pane1
+ n=$(( ($n + 3) % 9 ))
+ done
+ ;;
+ pane2)
+ tmux setw -q @pane2id $TMUX_PANE
+ tmux monitor -s pane2
+ tmux monitor -w pane2
+ columns=$(tput cols)
+ n=2
+ while true; do
+ for i in $(seq 1 $columns); do
+ sleep "0.1"
+ echo -n $n
+ done
+ echo
+ tmux monitor -s pane3
+ tmux monitor -w pane2
+ n=$(( ($n + 3) % 9 ))
+ done
+ ;;
+ pane3)
+ tmux setw -q @pane3id $TMUX_PANE
+ tmux monitor -s pane3
+ tmux monitor -w pane3
+ columns=$(tput cols)
+ n=3
+ while true; do
+ for i in $(seq 1 $columns); do
+ sleep "0.1"
+ echo -n $n
+ done
+ echo
+ tmux monitor -s pane1
+ tmux monitor -w pane3
+ n=$(( ($n + 3) % 9 ))
+ done
+ ;;
+ *)
+ columns=$(tput cols)
+ trap kill_child_panes SIGINT SIGTERM
+ clear
+ echo "This is a simple script that shows how tmux can synchronize"
+ echo "code running in different panes."
+ echo
+ echo "Besides recursively spliting panes, it will run a simple animation"
+ echo "demonstrating the coordination between panes"
+ echo
+ sleep 1
+ echo "First split horizontally"
+ tmux split-window "$abspath pane1"
+ tmux monitor -w pane1
+ pane1=`tmux showw -v @pane1id`
+ sleep 1
+ echo "Now we split the child pane 2 times"
+ tmux monitor -s pane1
+ tmux monitor -w pane3
+ tmux monitor -w pane2
+ pane2=`tmux showw -v @pane2id`
+ pane3=`tmux showw -v @pane3id`
+ column_width=$(($columns / 3))
+ sleep 1
+ echo "Resize equally"
+ tmux resize-pane -t $pane1 -x $column_width
+ tmux resize-pane -t $pane2 -x $column_width
+ tmux resize-pane -t $pane3 -x $column_width
+ sleep 1
+ echo "Start animation"
+ tmux monitor -s pane1
+ tmux select-pane -t $TMUX_PANE
+ while true; do
+ sleep 1000
+ done
+ ;;
+esac
+
diff --git a/monitor.c b/monitor.c
new file mode 100644
index 0000000..34c15dc
--- /dev/null
+++ b/monitor.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2013 Thiago de Arruda<tpadilh...@gmail.com>
+ *
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * This module adds features that allow scripts running in different panes
+ * to easily synchronize tasks. It exposes 'monitor' objects to tmux panes,
+ * which are similar to the monitor concept used in concurrent programming.
+ *
+ * The major difference is that this one manages/synchronizes tmux panes
+ * instead of threads.
+ *
+ * See http://en.wikipedia.org/wiki/Monitor_(synchronization) for more
+ * information about monitors
+*/
+
+RB_GENERATE(monitors, monitor_node, node, monitors_cmp);
+
+int
+monitors_cmp(struct monitor_node *m1, struct monitor_node *m2)
+{
+ return (strcmp(m1->name, m2->name));
+}
+
+void
+monitor_init(struct monitors *head)
+{
+ RB_INIT(head);
+}
+
+struct monitor_node *
+monitor_find(struct monitors *head, const char *name)
+{
+ struct monitor_node m_name;
+ m_name.name = (char *)name;
+ return RB_FIND(monitors, head, &m_name);
+}
+
+struct monitor_node *
+monitor_find_or_create(struct monitors *head, const char *name)
+{
+ struct monitor_node *m;
+
+ m = monitor_find(head, name);
+
+ if (m == NULL) {
+ /* Creates/insert a node for representing the monitor state. */
+ m = xmalloc(sizeof *m);
+ m->name = xstrdup(name);
+ m->count = 0;
+ m->pane = NULL;
+ TAILQ_INIT(&m->lock_queue);
+ TAILQ_INIT(&m->wait_stack);
+ RB_INSERT(monitors, head, m);
+ }
+
+ return m;
+}
+
+void
+monitor_signal_move_cmdq(struct monitors *head, struct monitor_node *m)
+{
+ struct cmdq_node *q;
+ /*
+ * Move all cmdq objects from the wait stack to the
+ * front of the lock queue in a way that the object
+ * in the bottom of the stack will be in the head
+ * of the queue(in other words, the first pane to
+ * enter the wait stack will be the first to awake).
+ */
+ while ((q = TAILQ_FIRST(&m->wait_stack)) != NULL) {
+ TAILQ_REMOVE(&m->wait_stack, q, node);
+ TAILQ_INSERT_HEAD(&m->lock_queue, q, node);
+ }
+ if (m->pane == NULL)
+ /* The monitor is unlocked, so start shifting. */
+ monitor_shift(head, m);
+}
+
+/*
+ * Entry point to the monitor which enforces the mutual exclusion property.
+ * All monitor actions have to go through here, the only exeception is
+ * monitor_unlock since the pane needs to already be occupying the monitor
+ * to use it.
+ */
+int
+monitor_enter(struct monitors *head, struct monitor_node *m, struct cmd_q *cmdq,
+ enum monitor_options options)
+{
+ struct window_pane *pane;
+ struct cmdq_node *q;
+
+ cmd_find_pane(cmdq, NULL, NULL, &pane);
+
+ if (m->pane == NULL || m->pane == pane) {
+ if (options & MONITOR_WAIT) {
+ cmdq->references++;
+ q = xmalloc(sizeof *q);
+ q->cmdq = cmdq;
+ /*
+ * Since this pane will enter the wait stack,
+ * we can remove the wait flag now.
+ */
+ q->options = options & ~MONITOR_WAIT;
+ /*
+ * If the lock was acquired as result of a wait command(meaning the pane
+ * wasn't holding the lock), then release as soon as it receives signal
+ */
+ if (m->pane != pane)
+ q->options |= MONITOR_RELEASE;
+ TAILQ_INSERT_HEAD(&m->wait_stack, q, node);
+ /* Release the pane lock. */
+ m->pane = NULL;
+ /* Waiting resets the number of locks acquired on the monitor. */
+ m->count = 0;
+ if (!TAILQ_EMPTY(&m->wait_stack))
+ monitor_shift(head, m);
+ } else if (options & MONITOR_SIGNAL) {
+ monitor_signal_move_cmdq(head, m);
+ } else {
+ /* Lock the monitor. */
+ m->pane = pane;
+ /*
+ * This implementation is reentrant, so the pane can lock the monitor
+ * multiple times, as long as it also releases it the same number of
+ * times.
+ */
+ m->count++;
+ }
+ return 1;
+ }
+
+ if (options & MONITOR_ENQUEUE) {
+ /* Push to the lock queue. */
+ cmdq->references++;
+ q = xmalloc(sizeof *q);
+ q->cmdq = cmdq;
+ /*
+ * If the monitor is already locked, and this came from a wait/signal
+ * command, then also release the lock as soon as it is acquired.
+ */
+ if ((options & MONITOR_WAIT) || (options & MONITOR_SIGNAL))
+ options |= MONITOR_RELEASE;
+ q->options = options;
+ TAILQ_INSERT_TAIL(&m->lock_queue, q, node);
+ }
+
+ return 0;
+}
+
+void
+monitor_shift(struct monitors *head, struct monitor_node *m)
+{
+ struct cmdq_node *q;
+
+ /* Release the lock. */
+ m->pane = NULL;
+
+ /* Process all panes that don't need to hold the lock(signaling/waiting panes). */
+ while ((q = TAILQ_FIRST(&m->lock_queue)) != NULL && (q->options & MONITOR_RELEASE)) {
+ TAILQ_REMOVE(&m->lock_queue, q, node);
+ if (q->options & MONITOR_SIGNAL)
+ monitor_signal_move_cmdq(head, m);
+ if (!cmdq_free(q->cmdq))
+ cmdq_continue(q->cmdq);
+ free(q);
+ }
+
+ /* Process a pane waiting to acquire the monitor lock. */
+ if (q != NULL) {
+ TAILQ_REMOVE(&m->lock_queue, q, node);
+ if (!monitor_enter(head, m, q->cmdq, (q->options & ~MONITOR_ENQUEUE)))
+ fatal("Failed to pass the monitor lock to the next pane");
+ if (!cmdq_free(q->cmdq))
+ cmdq_continue(q->cmdq);
+ free(q);
+ } else if (TAILQ_EMPTY(&m->wait_stack)) {
+ /*
+ * Monitor is unlocked and no pane is in the wait stack
+ * so now remove and free the monitor node.
+ */
+ RB_REMOVE(monitors, head, m);
+ free(m->name);
+ free(m);
+ }
+}
+
+
diff --git a/tmux.c b/tmux.c
index f685660..42900cc 100644
--- a/tmux.c
+++ b/tmux.c
@@ -37,6 +37,7 @@ struct options global_options; /* server options */
struct options global_s_options; /* session options */
struct options global_w_options; /* window options */
struct environ global_environ;
+struct monitors global_monitors;
struct event_base *ev_base;
@@ -336,6 +337,8 @@ main(int argc, char **argv)
options_init(&global_w_options, NULL);
options_table_populate_tree(window_options_table, &global_w_options);
+ monitor_init(&global_monitors);
+
/* Enable UTF-8 if the first client is on UTF-8 terminal. */
if (flags & IDENTIFY_UTF8) {
options_set_number(&global_s_options, "status-utf8", 1);
diff --git a/tmux.h b/tmux.h
index 0df1f4d..45cd876 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1486,6 +1486,64 @@ struct format_entry {
};
RB_HEAD(format_tree, format_entry);
+/* Monitor enter flags */
+
+enum monitor_options {
+ MONITOR_ENQUEUE = 1,
+ /*
+ * If this is set, as soon as the pane acquires the lock it will
+ * temporarily give up the lock rights and be put on the wait stack until
+ * a signal is received, at which point it will be put back in front
+ * of the lock queue again
+ */
+ MONITOR_WAIT = 2,
+ MONITOR_SIGNAL = 4,
+ /*
+ * If the pane is not holding the lock when it starts waiting for a signal,
+ * then this flag is set, which means that as soon as the the signal
+ * arrives, it will leave the wait stack and release the lock
+ */
+ MONITOR_RELEASE = 8
+};
+
+/*
+ * Queue of panes, used for tracking panes waiting to lock the monitor, or to
+ * receive a signal from another pane
+ */
+struct cmdq_node {
+
+ struct cmd_q *cmdq;
+
+ enum monitor_options options;
+
+ TAILQ_ENTRY(cmdq_node) node;
+};
+
+/*
+ * Tree that keeps track of monitors. Each monitor is identified by a
+ * name, and the monitor node only exists while there's some pane using it
+ */
+struct monitor_node {
+
+ /* Monitor name. */
+ char *name;
+
+ /* Pane locking the monitor. */
+ struct window_pane *pane;
+
+ /* Number of times the pane has locked the monitor. */
+ int count;
+
+ /* Panes waiting to lock the monitor. */
+ TAILQ_HEAD(, cmdq_node) lock_queue;
+
+ /* Panes waiting for signal. */
+ TAILQ_HEAD(, cmdq_node) wait_stack;
+
+ RB_ENTRY(monitor_node) node;
+};
+RB_HEAD(monitors, monitor_node);
+
/* Common command usages. */
#define CMD_TARGET_PANE_USAGE "[-t target-pane]"
#define CMD_TARGET_WINDOW_USAGE "[-t target-window]"
@@ -1502,6 +1560,7 @@ extern struct options global_options;
extern struct options global_s_options;
extern struct options global_w_options;
extern struct environ global_environ;
+extern struct monitors global_monitors;
extern struct event_base *ev_base;
extern char *cfg_file;
extern char *shell_cmd;
@@ -1793,6 +1852,7 @@ extern const struct cmd_entry cmd_lock_server_entry;
extern const struct cmd_entry cmd_lock_session_entry;
extern const struct cmd_entry cmd_move_pane_entry;
extern const struct cmd_entry cmd_move_window_entry;
+extern const struct cmd_entry cmd_monitor_entry;
extern const struct cmd_entry cmd_new_session_entry;
extern const struct cmd_entry cmd_new_window_entry;
extern const struct cmd_entry cmd_next_layout_entry;
@@ -2346,4 +2406,15 @@ int xvasprintf(char **, const char *, va_list);
int printflike3 xsnprintf(char *, size_t, const char *, ...);
int xvsnprintf(char *, size_t, const char *, va_list);
+/* monitor.c */
+int monitors_cmp(struct monitor_node *, struct monitor_node *);
+RB_PROTOTYPE(monitors, monitor_node, node, monitors_cmp);
+void monitor_init(struct monitors *);
+struct monitor_node *monitor_find(struct monitors *, const char *);
+struct monitor_node *monitor_find_or_create(struct monitors *, const char *);
+void monitor_signal_move_cmdq(struct monitors *, struct monitor_node *);
+int monitor_enter(struct monitors *, struct monitor_node *, struct cmd_q *cmdq,
+ enum monitor_options);
+void monitor_shift(struct monitors *, struct monitor_node *);
+
#endif /* TMUX_H */
diff --git a/monitor.c b/monitor.c
index 34c15dc..35a57a5 100644
--- a/monitor.c
+++ b/monitor.c
@@ -166,11 +166,41 @@ monitor_enter(struct monitors *head, struct monitor_node *m, struct cmd_q *cmdq,
return 0;
}
+struct shift_evdata {
+ struct monitors *head;
+ char *name;
+};
+
void
monitor_shift(struct monitors *head, struct monitor_node *m)
{
+ struct shift_evdata *evdata;
+ struct event ev;
+ struct timeval tv = {0, 0};
+
+ evdata = xmalloc(sizeof *evdata);
+ evdata->head = head;
+ evdata->name = xstrdup(m->name);
+ evtimer_set(&ev, monitor_shift_handler, evdata);
+ evtimer_add(&ev, &tv);
+}
+
+void monitor_shift_handler(int fd, short ev, void *data)
+{
+ struct shift_evdata *evdata = (struct shift_evdata *)data;
+ struct monitors *head = evdata->head;
+ struct monitor_node *m = monitor_find(head, evdata->name);
struct cmdq_node *q;
+ (void)fd;
+ (void)ev;
+
+ free(evdata->name);
+ free(evdata);
+
+ if (m == NULL)
+ return;
+
/* Release the lock. */
m->pane = NULL;
@@ -202,5 +232,3 @@ monitor_shift(struct monitors *head, struct monitor_node *m)
free(m);
}
}
-
-
diff --git a/tmux.h b/tmux.h
index 45cd876..50a85c6 100644
--- a/tmux.h
+++ b/tmux.h
@@ -2416,5 +2416,6 @@ void monitor_signal_move_cmdq(struct monitors *, struct monitor_node *);
int monitor_enter(struct monitors *, struct monitor_node *, struct cmd_q *cmdq,
enum monitor_options);
void monitor_shift(struct monitors *, struct monitor_node *);
+void monitor_shift_handler(int, short, void *);
#endif /* TMUX_H */
------------------------------------------------------------------------------
Everyone hates slow websites. So do we.
Make your web apps faster with AppDynamics
Download AppDynamics Lite for free today:
http://p.sf.net/sfu/appdyn_d2d_feb
_______________________________________________
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users