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 <n...@users.sourceforge.net> + * 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 <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<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" > + > +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 > tmux-users@lists.sourceforge.net > 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 tmux-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/tmux-users