Ok this looks pretty good. I've made a few changes but mainly style/layout nits:
- I don't think we need find and find_and_create functions, the find
will only be done once and the create twice (when there is lock too).
- A single cmdq can only be waiting once so no need to have a wrapper
struct, just put the TAILQ_ENTRY in struct cmd_q itself.
- channel_node -> wait_channel and similar renaming.
- We always include sys/types.h, needed or not. Also errors always start
with lowercase.
- Move things about to sort of vaguely fit where they go in other
commands.
- Add to man page.
I considered putting structs in tmux.h but I can't see how they would be
needed outside this file so let's leave them local anyway. tmux.h is too
big already.
Please take a look and make sure there isn't anything stupid and then I
will commit and we can modify to add locking.
diff --git a/Makefile.am b/Makefile.am
index 5caa498..19220d8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -135,6 +135,7 @@ dist_tmux_SOURCES = \
cmd-switch-client.c \
cmd-unbind-key.c \
cmd-unlink-window.c \
+ cmd-wait-for.c \
cmd.c \
colour.c \
control.c \
diff --git a/cmd-wait-for.c b/cmd-wait-for.c
new file mode 100644
index 0000000..6313358
--- /dev/null
+++ b/cmd-wait-for.c
@@ -0,0 +1,124 @@
+/* $Id$ */
+
+/*
+ * Copyright (c) 2013 Nicholas Marriott <[email protected]>
+ * Copyright (c) 2013 Thiago de Arruda <[email protected]>
+ *
+ * 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 <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Block or wake a client on a named wait channel.
+ */
+
+enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmd_q *);
+
+const struct cmd_entry cmd_wait_for_entry = {
+ "wait-for", "wait",
+ "S", 1, 1,
+ "[-S] channel",
+ 0,
+ NULL,
+ NULL,
+ cmd_wait_for_exec
+};
+
+struct wait_channel {
+ const char *name;
+ TAILQ_HEAD(, cmd_q) waiters;
+
+ RB_ENTRY(wait_channel) entry;
+};
+RB_HEAD(wait_channels, wait_channel);
+struct wait_channels wait_channels = RB_INITIALIZER(wait_channels);
+
+int wait_channel_cmp(struct wait_channel *, struct wait_channel *);
+RB_PROTOTYPE(wait_channels, wait_channel, entry, wait_channel_cmp);
+RB_GENERATE(wait_channels, wait_channel, entry, wait_channel_cmp);
+
+int
+wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2)
+{
+ return (strcmp(wc1->name, wc2->name));
+}
+
+enum cmd_retval cmd_wait_for_signal(struct cmd_q *, const char *,
+ struct wait_channel *);
+enum cmd_retval cmd_wait_for_wait(struct cmd_q *, const char *,
+ struct wait_channel *);
+
+enum cmd_retval
+cmd_wait_for_exec(struct cmd *self, struct cmd_q *cmdq)
+{
+ struct args *args = self->args;
+ const char *name = args->argv[0];
+ struct wait_channel *wc, wc0;
+
+ wc0.name = name;
+ wc = RB_FIND(wait_channels, &wait_channels, &wc0);
+
+ if (args_has(args, 'S'))
+ return (cmd_wait_for_signal(cmdq, name, wc));
+ return (cmd_wait_for_wait(cmdq, name, wc));
+}
+
+enum cmd_retval
+cmd_wait_for_signal(struct cmd_q *cmdq, const char *name,
+ struct wait_channel *wc)
+{
+ struct cmd_q *wq, *wq1;
+
+ if (wc == NULL || TAILQ_EMPTY(&wc->waiters)) {
+ cmdq_error(cmdq, "no waiting clients on %s", name);
+ return (CMD_RETURN_ERROR);
+ }
+
+ TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
+ TAILQ_REMOVE(&wc->waiters, wq, waitentry);
+ if (!cmdq_free(wq))
+ cmdq_continue(wq);
+ }
+ RB_REMOVE(wait_channels, &wait_channels, wc);
+ free((void*) wc->name);
+ free(wc);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+enum cmd_retval
+cmd_wait_for_wait(struct cmd_q *cmdq, const char *name,
+ struct wait_channel *wc)
+{
+ if (cmdq->client == NULL || cmdq->client->session != NULL) {
+ cmdq_error(cmdq, "not able to wait");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (wc == NULL) {
+ wc = xmalloc(sizeof *wc);
+ wc->name = xstrdup(name);
+ TAILQ_INIT(&wc->waiters);
+ RB_INSERT(wait_channels, &wait_channels, wc);
+ }
+ TAILQ_INSERT_TAIL(&wc->waiters, cmdq, waitentry);
+ cmdq->references++;
+
+ return (CMD_RETURN_WAIT);
+}
diff --git a/cmd.c b/cmd.c
index 0d6a85f..20484ed 100644
--- a/cmd.c
+++ b/cmd.c
@@ -112,6 +112,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_switch_client_entry,
&cmd_unbind_key_entry,
&cmd_unlink_window_entry,
+ &cmd_wait_for_entry,
NULL
};
diff --git a/tmux.1 b/tmux.1
index 1a9c058..8df7975 100644
--- a/tmux.1
+++ b/tmux.1
@@ -3553,6 +3553,19 @@ If the command doesn't return success, the exit status
is also displayed.
.It Ic server-info
.D1 (alias: Ic info )
Show server information and terminal details.
+.It Xo Ic wait-for
+.Fl S
+.Ar channel
+.Xc
+.D1 (alias: Ic wait )
+When used without
+.Fl S ,
+prevents the client from exiting until woken using
+.Ic wait-for
+.Fl S
+with the same channel.
+This command only works from outside
+.Nm .
.El
.Sh TERMINFO EXTENSIONS
.Nm
diff --git a/tmux.h b/tmux.h
index c1ad662..e58c1de 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1416,6 +1416,8 @@ struct cmd_q {
void *data;
struct msg_command_data *msgdata;
+
+ TAILQ_ENTRY(cmd_q) waitentry;
};
/* Command definition. */
@@ -1835,6 +1837,7 @@ extern const struct cmd_entry cmd_switch_client_entry;
extern const struct cmd_entry cmd_unbind_key_entry;
extern const struct cmd_entry cmd_unlink_window_entry;
extern const struct cmd_entry cmd_up_pane_entry;
+extern const struct cmd_entry cmd_wait_for_entry;
/* cmd-attach-session.c */
enum cmd_retval cmd_attach_session(struct cmd_q *, const char*, int,
int);
On Tue, Mar 05, 2013 at 10:55:05PM -0300, Thiago Padilha wrote:
> Here it goes:
>
> The first patch implements wait/signal, the second extends it with
> lock/unlock. My goal this time was to make the code small and simple
> as possible, let me know if you think anything needs to be refactored.
> ---
> Makefile.am | 1 +
> cmd-wait-for.c | 145
> ++++++++++++++++++++++++++++++++++++++++++++++
> cmd.c | 1 +
> examples/tmux-wait-for.sh | 109 ++++++++++++++++++++++++++++++++++
> tmux.h | 1 +
> 5 files changed, 257 insertions(+)
> create mode 100644 cmd-wait-for.c
> create mode 100755 examples/tmux-wait-for.sh
> diff --git a/Makefile.am b/Makefile.am
> index 5caa498..19220d8 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -135,6 +135,7 @@ dist_tmux_SOURCES = \
> cmd-switch-client.c \
> cmd-unbind-key.c \
> cmd-unlink-window.c \
> + cmd-wait-for.c \
> cmd.c \
> colour.c \
> control.c \
> diff --git a/cmd-wait-for.c b/cmd-wait-for.c
> new file mode 100644
> index 0000000..658109b
> --- /dev/null
> +++ b/cmd-wait-for.c
> @@ -0,0 +1,145 @@
> +/* $Id$ */
> +
> +/*
> + * Copyright (c) 2013 Thiago de Arruda<[email protected]>
> + *
> + *
> + * 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"
> +
> +struct cmdq_node {
> + struct cmd_q *cmdq;
> + TAILQ_ENTRY(cmdq_node) node;
> +};
> +
> +struct channel_node {
> + char *name;
> + TAILQ_HEAD(, cmdq_node) waiting_cmdqs;
> + RB_ENTRY(channel_node) node;
> +};
> +RB_HEAD(channels, channel_node) channels_head =
> RB_INITIALIZER(&channels_head);
> +
> +enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmd_q *);
> +enum cmd_retval channel_wait(const char *, struct cmd_q *);
> +enum cmd_retval channel_signal(const char *, struct cmd_q *);
> +struct channel_node *channels_find(const char *);
> +struct channel_node *channels_find_or_create(const char *);
> +int channel_cmp(struct channel_node *, struct channel_node *);
> +RB_PROTOTYPE(channels, channel_node, node, channel_cmp);
> +
> +const struct cmd_entry cmd_wait_for_entry = {
> + "wait-for", "wait",
> + "S", 1, 1,
> + "[-S] channel",
> + 0,
> + NULL,
> + NULL,
> + cmd_wait_for_exec
> +};
> +
> +enum cmd_retval
> +cmd_wait_for_exec(struct cmd *self, struct cmd_q *cmdq)
> +{
> + struct args *args = self->args;
> + const char *name = args->argv[0];
> +
> + if (args_has(args, 'S'))
> + return channel_signal(name, cmdq);
> +
> + return channel_wait(name, cmdq);
> +}
> +
> +RB_GENERATE(channels, channel_node, node, channel_cmp);
> +
> +int
> +channel_cmp(struct channel_node *c1, struct channel_node *c2)
> +{
> + return (strcmp(c1->name, c2->name));
> +}
> +
> +struct channel_node *
> +channels_find(const char *name)
> +{
> + struct channel_node c_name;
> + c_name.name = (char *)name;
> + return RB_FIND(channels, &channels_head, &c_name);
> +}
> +
> +struct channel_node *
> +channels_find_or_create(const char *name)
> +{
> + struct channel_node *c;
> +
> + c = channels_find(name);
> +
> + if (c == NULL) {
> + c = xmalloc(sizeof *c);
> + c->name = xstrdup(name);
> + TAILQ_INIT(&c->waiting_cmdqs);
> + RB_INSERT(channels, &channels_head, c);
> + }
> +
> + return c;
> +}
> +
> +enum cmd_retval
> +channel_wait(const char *name, struct cmd_q *cmdq)
> +{
> + struct cmdq_node *wq;
> + struct channel_node *c;
> +
> + if (cmdq->client == NULL || cmdq->client->session != NULL) {
> + cmdq_error(cmdq, "Not able to wait");
> + return (CMD_RETURN_ERROR);
> + }
> +
> + c = channels_find_or_create(name);
> + wq = xmalloc(sizeof *wq);
> + wq->cmdq = cmdq;
> + TAILQ_INSERT_HEAD(&c->waiting_cmdqs, wq, node);
> + cmdq->references++;
> +
> + return (CMD_RETURN_WAIT);
> +}
> +
> +enum cmd_retval
> +channel_signal(const char *name, struct cmd_q *cmdq)
> +{
> + struct cmdq_node *wq;
> + struct channel_node *c;
> +
> + c = channels_find(name);
> +
> + if (c == NULL || TAILQ_EMPTY(&c->waiting_cmdqs)) {
> + cmdq_error(cmdq, "No waiting clients");
> + return (CMD_RETURN_ERROR);
> + }
> +
> + while ((wq = TAILQ_FIRST(&c->waiting_cmdqs)) != NULL) {
> + TAILQ_REMOVE(&c->waiting_cmdqs, wq, node);
> + if (!cmdq_free(wq->cmdq))
> + cmdq_continue(wq->cmdq);
> + free(wq);
> + }
> +
> + RB_REMOVE(channels, &channels_head, c);
> + free(c->name);
> + free(c);
> +
> + return (CMD_RETURN_NORMAL);
> +}
> diff --git a/cmd.c b/cmd.c
> index 0d6a85f..20484ed 100644
> --- a/cmd.c
> +++ b/cmd.c
> @@ -112,6 +112,7 @@ const struct cmd_entry *cmd_table[] = {
> &cmd_switch_client_entry,
> &cmd_unbind_key_entry,
> &cmd_unlink_window_entry,
> + &cmd_wait_for_entry,
> NULL
> };
>
> diff --git a/examples/tmux-wait-for.sh b/examples/tmux-wait-for.sh
> new file mode 100755
> index 0000000..94baa90
> --- /dev/null
> +++ b/examples/tmux-wait-for.sh
> @@ -0,0 +1,109 @@
> +#!/bin/bash
> +
> +# Shows how one can synchronize work using the 'wait' command
> +
> +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 wait -S pane1
> + tmux wait pane1
> + tmux split-window -d -h "$abspath pane3"
> + tmux split-window -d -h "$abspath pane2"
> + tmux wait 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 wait -S pane2
> + tmux wait pane1
> + n=$(( ($n + 3) % 9 ))
> + done
> + ;;
> + pane2)
> + tmux setw -q @pane2id $TMUX_PANE
> + tmux wait -S pane2
> + tmux wait 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 wait -S pane3
> + tmux wait pane2
> + n=$(( ($n + 3) % 9 ))
> + done
> + ;;
> + pane3)
> + tmux setw -q @pane3id $TMUX_PANE
> + tmux wait -S pane3
> + tmux wait 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 wait -S pane1
> + tmux wait 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 wait pane1
> + pane1=`tmux showw -v @pane1id`
> + sleep 1
> + echo "Now we split the child pane 2 times"
> + tmux wait -S pane1
> + tmux wait pane3
> + tmux wait 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 wait -S pane1
> + tmux select-pane -t $TMUX_PANE
> + while true; do
> + sleep 1000
> + done
> + ;;
> +esac
> diff --git a/tmux.h b/tmux.h
> index c1ad662..95dd97c 100644
> --- a/tmux.h
> +++ b/tmux.h
> @@ -1835,6 +1835,7 @@ extern const struct cmd_entry cmd_switch_client_entry;
> extern const struct cmd_entry cmd_unbind_key_entry;
> extern const struct cmd_entry cmd_unlink_window_entry;
> extern const struct cmd_entry cmd_up_pane_entry;
> +extern const struct cmd_entry cmd_wait_for_entry;
>
> /* cmd-attach-session.c */
> enum cmd_retval cmd_attach_session(struct cmd_q *, const char*, int,
> int);
> diff --git a/cmd-wait-for.c b/cmd-wait-for.c
> index 658109b..58e53c6 100644
> --- a/cmd-wait-for.c
> +++ b/cmd-wait-for.c
> @@ -29,7 +29,9 @@ struct cmdq_node {
>
> struct channel_node {
> char *name;
> + int locked;
> TAILQ_HEAD(, cmdq_node) waiting_cmdqs;
> + TAILQ_HEAD(, cmdq_node) locking_cmdqs;
> RB_ENTRY(channel_node) node;
> };
> RB_HEAD(channels, channel_node) channels_head =
> RB_INITIALIZER(&channels_head);
> @@ -37,6 +39,8 @@ RB_HEAD(channels, channel_node) channels_head =
> RB_INITIALIZER(&channels_head);
> enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmd_q *);
> enum cmd_retval channel_wait(const char *, struct cmd_q *);
> enum cmd_retval channel_signal(const char *, struct cmd_q *);
> +enum cmd_retval channel_lock(const char *, struct cmd_q *);
> +enum cmd_retval channel_unlock(const char *, struct cmd_q *);
> struct channel_node *channels_find(const char *);
> struct channel_node *channels_find_or_create(const char *);
> int channel_cmp(struct channel_node *, struct channel_node *);
> @@ -44,8 +48,8 @@ RB_PROTOTYPE(channels, channel_node, node, channel_cmp);
>
> const struct cmd_entry cmd_wait_for_entry = {
> "wait-for", "wait",
> - "S", 1, 1,
> - "[-S] channel",
> + "LSU", 1, 1,
> + "[-L | -S | -U] channel",
> 0,
> NULL,
> NULL,
> @@ -61,6 +65,12 @@ cmd_wait_for_exec(struct cmd *self, struct cmd_q *cmdq)
> if (args_has(args, 'S'))
> return channel_signal(name, cmdq);
>
> + if (args_has(args, 'L'))
> + return channel_lock(name, cmdq);
> +
> + if (args_has(args, 'U'))
> + return channel_unlock(name, cmdq);
> +
> return channel_wait(name, cmdq);
> }
>
> @@ -90,7 +100,9 @@ channels_find_or_create(const char *name)
> if (c == NULL) {
> c = xmalloc(sizeof *c);
> c->name = xstrdup(name);
> + c->locked = 0;
> TAILQ_INIT(&c->waiting_cmdqs);
> + TAILQ_INIT(&c->locking_cmdqs);
> RB_INSERT(channels, &channels_head, c);
> }
>
> @@ -137,9 +149,67 @@ channel_signal(const char *name, struct cmd_q *cmdq)
> free(wq);
> }
>
> - RB_REMOVE(channels, &channels_head, c);
> - free(c->name);
> - free(c);
> + if (!c->locked) {
> + RB_REMOVE(channels, &channels_head, c);
> + free(c->name);
> + free(c);
> + }
>
> return (CMD_RETURN_NORMAL);
> }
> +
> +enum cmd_retval
> +channel_lock(const char *name, struct cmd_q *cmdq)
> +{
> + struct cmdq_node *lq;
> + struct channel_node *c;
> +
> + if (cmdq->client == NULL || cmdq->client->session != NULL) {
> + cmdq_error(cmdq, "Not able to lock");
> + return (CMD_RETURN_ERROR);
> + }
> +
> + c = channels_find_or_create(name);
> +
> + if (c->locked) {
> + lq = xmalloc(sizeof *lq);
> + lq->cmdq = cmdq;
> + TAILQ_INSERT_TAIL(&c->locking_cmdqs, lq, node);
> + cmdq->references++;
> + return (CMD_RETURN_WAIT);
> + }
> +
> + c->locked = 1;
> + return (CMD_RETURN_NORMAL);
> +}
> +
> +enum cmd_retval
> +channel_unlock(const char *name, struct cmd_q *cmdq)
> +{
> + struct cmdq_node *lq;
> + struct channel_node *c;
> +
> + c = channels_find(name);
> +
> + if (c == NULL || !c->locked) {
> + cmdq_error(cmdq, "Channel not locked");
> + return (CMD_RETURN_ERROR);
> + }
> +
> + if ((lq = TAILQ_FIRST(&c->locking_cmdqs)) != NULL) {
> + TAILQ_REMOVE(&c->locking_cmdqs, lq, node);
> + if (!cmdq_free(lq->cmdq))
> + cmdq_continue(lq->cmdq);
> + free(lq);
> + } else {
> + c->locked = 0;
> + if (TAILQ_EMPTY(&c->waiting_cmdqs)) {
> + RB_REMOVE(channels, &channels_head, c);
> + free(c->name);
> + free(c);
> + }
> + }
> +
> + return (CMD_RETURN_NORMAL);
> +}
> +
> ------------------------------------------------------------------------------
> Symantec Endpoint Protection 12 positioned as A LEADER in The Forrester
> Wave(TM): Endpoint Security, Q1 2013 and "remains a good choice" in the
> endpoint security space. For insight on selecting the right partner to
> tackle endpoint security challenges, access the full report.
> http://p.sf.net/sfu/symantec-dev2dev
> _______________________________________________
> tmux-users mailing list
> [email protected]
> https://lists.sourceforge.net/lists/listinfo/tmux-users
------------------------------------------------------------------------------
Symantec Endpoint Protection 12 positioned as A LEADER in The Forrester
Wave(TM): Endpoint Security, Q1 2013 and "remains a good choice" in the
endpoint security space. For insight on selecting the right partner to
tackle endpoint security challenges, access the full report.
http://p.sf.net/sfu/symantec-dev2dev
_______________________________________________
tmux-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tmux-users