I expressed myself badly. I meant using your commit as a model for
organizing my code, not base the work on it.

This patch contains the refactored version and it should be possible
to apply on current master
---
 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..d0f3a13
--- /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;
+		SIMPLEQ_INIT(&m->lock_queue);
+		SIMPLEQ_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 = SIMPLEQ_FIRST(&m->wait_stack)) != NULL) {
+		SIMPLEQ_REMOVE_HEAD(&m->wait_stack, node);
+		SIMPLEQ_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;
+			SIMPLEQ_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 (!SIMPLEQ_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;
+		SIMPLEQ_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 = SIMPLEQ_FIRST(&m->lock_queue)) != NULL && (q->options & MONITOR_RELEASE)) {
+		SIMPLEQ_REMOVE_HEAD(&m->lock_queue, 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) {
+		SIMPLEQ_REMOVE_HEAD(&m->lock_queue, 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 (SIMPLEQ_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..94b3719 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;
+
+	SIMPLEQ_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. */
+	SIMPLEQ_HEAD(, cmdq_node) lock_queue;
+
+	/* Panes waiting for signal. */
+	SIMPLEQ_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 */
------------------------------------------------------------------------------
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

Reply via email to