Patch 8.0.0027
Problem: A channel is closed when reading on stderr or stdout fails, but
there may still be something to read on another part.
Solution: Turn ch_to_be_closed into a bitfield. (Ozaki Kiichi)
Files: src/channel.c, src/eval.c, src/structs.h, src/proto/channel.pro,
src/testdir/test_channel.vim
*** ../vim-8.0.0026/src/channel.c 2016-10-09 15:43:22.455026647 +0200
--- src/channel.c 2016-10-09 17:08:57.303345580 +0200
***************
*** 54,60 ****
# define fd_close(sd) close(sd)
#endif
! static void channel_read(channel_T *channel, int part, char *func);
/* Whether a redraw is needed for appending a line to a buffer. */
static int channel_need_redraw = FALSE;
--- 54,60 ----
# define fd_close(sd) close(sd)
#endif
! static void channel_read(channel_T *channel, ch_part_T part, char *func);
/* Whether a redraw is needed for appending a line to a buffer. */
static int channel_need_redraw = FALSE;
***************
*** 309,315 ****
channel_T *
add_channel(void)
{
! int part;
channel_T *channel = (channel_T *)alloc_clear((int)sizeof(channel_T));
if (channel == NULL)
--- 309,315 ----
channel_T *
add_channel(void)
{
! ch_part_T part;
channel_T *channel = (channel_T *)alloc_clear((int)sizeof(channel_T));
if (channel == NULL)
***************
*** 318,324 ****
channel->ch_id = next_ch_id++;
ch_log(channel, "Created channel");
! for (part = PART_SOCK; part <= PART_IN; ++part)
{
channel->ch_part[part].ch_fd = INVALID_FD;
#ifdef FEAT_GUI_X11
--- 318,324 ----
channel->ch_id = next_ch_id++;
ch_log(channel, "Created channel");
! for (part = PART_SOCK; part < PART_COUNT; ++part)
{
channel->ch_part[part].ch_fd = INVALID_FD;
#ifdef FEAT_GUI_X11
***************
*** 421,429 ****
if (!in_free_unref_items)
{
if (safe_to_invoke_callback == 0)
- {
channel->ch_to_be_freed = TRUE;
- }
else
{
channel_free_contents(channel);
--- 421,427 ----
***************
*** 511,517 ****
channel_read_fd(int fd)
{
channel_T *channel;
! int part;
channel = channel_fd2channel(fd, &part);
if (channel == NULL)
--- 509,515 ----
channel_read_fd(int fd)
{
channel_T *channel;
! ch_part_T part;
channel = channel_fd2channel(fd, &part);
if (channel == NULL)
***************
*** 557,563 ****
#endif
static void
! channel_gui_register_one(channel_T *channel, int part)
{
if (!CH_HAS_GUI)
return;
--- 555,561 ----
#endif
static void
! channel_gui_register_one(channel_T *channel, ch_part_T part)
{
if (!CH_HAS_GUI)
return;
***************
*** 627,633 ****
}
static void
! channel_gui_unregister_one(channel_T *channel, int part)
{
# ifdef FEAT_GUI_X11
if (channel->ch_part[part].ch_inputHandler != (XtInputId)NULL)
--- 625,631 ----
}
static void
! channel_gui_unregister_one(channel_T *channel, ch_part_T part)
{
# ifdef FEAT_GUI_X11
if (channel->ch_part[part].ch_inputHandler != (XtInputId)NULL)
***************
*** 653,659 ****
static void
channel_gui_unregister(channel_T *channel)
{
! int part;
for (part = PART_SOCK; part < PART_IN; ++part)
channel_gui_unregister_one(channel, part);
--- 651,657 ----
static void
channel_gui_unregister(channel_T *channel)
{
! ch_part_T part;
for (part = PART_SOCK; part < PART_IN; ++part)
channel_gui_unregister_one(channel, part);
***************
*** 928,933 ****
--- 926,932 ----
channel->ch_nb_close_cb = nb_close_cb;
channel->ch_hostname = (char *)vim_strsave((char_u *)hostname);
channel->ch_port = port_in;
+ channel->ch_to_be_closed |= (1 << PART_SOCK);
#ifdef FEAT_GUI
channel_gui_register_one(channel, PART_SOCK);
***************
*** 998,1009 ****
}
static void
! may_close_part(sock_T *fd)
{
if (*fd != INVALID_FD)
{
! fd_close(*fd);
*fd = INVALID_FD;
}
}
--- 997,1015 ----
}
static void
! ch_close_part(channel_T *channel, ch_part_T part)
{
+ sock_T *fd = &channel->ch_part[part].ch_fd;
+
if (*fd != INVALID_FD)
{
! if (part == PART_SOCK)
! sock_close(*fd);
! else
! fd_close(*fd);
*fd = INVALID_FD;
+
+ channel->ch_to_be_closed &= ~(1 << part);
}
}
***************
*** 1012,1018 ****
{
if (in != INVALID_FD)
{
! may_close_part(&channel->CH_IN_FD);
channel->CH_IN_FD = in;
}
if (out != INVALID_FD)
--- 1018,1024 ----
{
if (in != INVALID_FD)
{
! ch_close_part(channel, PART_IN);
channel->CH_IN_FD = in;
}
if (out != INVALID_FD)
***************
*** 1020,1027 ****
# if defined(FEAT_GUI)
channel_gui_unregister_one(channel, PART_OUT);
# endif
! may_close_part(&channel->CH_OUT_FD);
channel->CH_OUT_FD = out;
# if defined(FEAT_GUI)
channel_gui_register_one(channel, PART_OUT);
# endif
--- 1026,1034 ----
# if defined(FEAT_GUI)
channel_gui_unregister_one(channel, PART_OUT);
# endif
! ch_close_part(channel, PART_OUT);
channel->CH_OUT_FD = out;
+ channel->ch_to_be_closed |= (1 << PART_OUT);
# if defined(FEAT_GUI)
channel_gui_register_one(channel, PART_OUT);
# endif
***************
*** 1031,1038 ****
# if defined(FEAT_GUI)
channel_gui_unregister_one(channel, PART_ERR);
# endif
! may_close_part(&channel->CH_ERR_FD);
channel->CH_ERR_FD = err;
# if defined(FEAT_GUI)
channel_gui_register_one(channel, PART_ERR);
# endif
--- 1038,1046 ----
# if defined(FEAT_GUI)
channel_gui_unregister_one(channel, PART_ERR);
# endif
! ch_close_part(channel, PART_ERR);
channel->CH_ERR_FD = err;
+ channel->ch_to_be_closed |= (1 << PART_ERR);
# if defined(FEAT_GUI)
channel_gui_register_one(channel, PART_ERR);
# endif
***************
*** 1151,1160 ****
void
channel_set_options(channel_T *channel, jobopt_T *opt)
{
! int part;
if (opt->jo_set & JO_MODE)
! for (part = PART_SOCK; part <= PART_IN; ++part)
channel->ch_part[part].ch_mode = opt->jo_mode;
if (opt->jo_set & JO_IN_MODE)
channel->ch_part[PART_IN].ch_mode = opt->jo_in_mode;
--- 1159,1168 ----
void
channel_set_options(channel_T *channel, jobopt_T *opt)
{
! ch_part_T part;
if (opt->jo_set & JO_MODE)
! for (part = PART_SOCK; part < PART_COUNT; ++part)
channel->ch_part[part].ch_mode = opt->jo_mode;
if (opt->jo_set & JO_IN_MODE)
channel->ch_part[PART_IN].ch_mode = opt->jo_in_mode;
***************
*** 1164,1170 ****
channel->ch_part[PART_ERR].ch_mode = opt->jo_err_mode;
if (opt->jo_set & JO_TIMEOUT)
! for (part = PART_SOCK; part <= PART_IN; ++part)
channel->ch_part[part].ch_timeout = opt->jo_timeout;
if (opt->jo_set & JO_OUT_TIMEOUT)
channel->ch_part[PART_OUT].ch_timeout = opt->jo_out_timeout;
--- 1172,1178 ----
channel->ch_part[PART_ERR].ch_mode = opt->jo_err_mode;
if (opt->jo_set & JO_TIMEOUT)
! for (part = PART_SOCK; part < PART_COUNT; ++part)
channel->ch_part[part].ch_timeout = opt->jo_timeout;
if (opt->jo_set & JO_OUT_TIMEOUT)
channel->ch_part[PART_OUT].ch_timeout = opt->jo_out_timeout;
***************
*** 1282,1288 ****
void
channel_set_req_callback(
channel_T *channel,
! int part,
char_u *callback,
partial_T *partial,
int id)
--- 1290,1296 ----
void
channel_set_req_callback(
channel_T *channel,
! ch_part_T part,
char_u *callback,
partial_T *partial,
int id)
***************
*** 1448,1454 ****
ch_log(channel, "Finished writing all lines to channel");
/* Close the pipe/socket, so that the other side gets EOF. */
! may_close_part(&channel->CH_IN_FD);
}
else
ch_logn(channel, "Still %d more lines to write",
--- 1456,1462 ----
ch_log(channel, "Finished writing all lines to channel");
/* Close the pipe/socket, so that the other side gets EOF. */
! ch_close_part(channel, PART_IN);
}
else
ch_logn(channel, "Still %d more lines to write",
***************
*** 1462,1471 ****
channel_buffer_free(buf_T *buf)
{
channel_T *channel;
! int part;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
! for (part = PART_SOCK; part <= PART_IN; ++part)
{
chanpart_T *ch_part = &channel->ch_part[part];
--- 1470,1479 ----
channel_buffer_free(buf_T *buf)
{
channel_T *channel;
! ch_part_T part;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
! for (part = PART_SOCK; part < PART_COUNT; ++part)
{
chanpart_T *ch_part = &channel->ch_part[part];
***************
*** 1574,1580 ****
* Returns NULL if there is nothing.
*/
readq_T *
! channel_peek(channel_T *channel, int part)
{
readq_T *head = &channel->ch_part[part].ch_head;
--- 1582,1588 ----
* Returns NULL if there is nothing.
*/
readq_T *
! channel_peek(channel_T *channel, ch_part_T part)
{
readq_T *head = &channel->ch_part[part].ch_head;
***************
*** 1604,1610 ****
* Returns NULL if there is nothing.
*/
char_u *
! channel_get(channel_T *channel, int part)
{
readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next;
--- 1612,1618 ----
* Returns NULL if there is nothing.
*/
char_u *
! channel_get(channel_T *channel, ch_part_T part)
{
readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next;
***************
*** 1628,1634 ****
* Replaces NUL bytes with NL.
*/
static char_u *
! channel_get_all(channel_T *channel, int part)
{
readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next;
--- 1636,1642 ----
* Replaces NUL bytes with NL.
*/
static char_u *
! channel_get_all(channel_T *channel, ch_part_T part)
{
readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next;
***************
*** 1677,1683 ****
* Caller must check these bytes are available.
*/
void
! channel_consume(channel_T *channel, int part, int len)
{
readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next;
--- 1685,1691 ----
* Caller must check these bytes are available.
*/
void
! channel_consume(channel_T *channel, ch_part_T part, int len)
{
readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next;
***************
*** 1693,1699 ****
* When "want_nl" is TRUE collapse more buffers until a NL is found.
*/
int
! channel_collapse(channel_T *channel, int part, int want_nl)
{
readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next;
--- 1701,1707 ----
* When "want_nl" is TRUE collapse more buffers until a NL is found.
*/
int
! channel_collapse(channel_T *channel, ch_part_T part, int want_nl)
{
readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next;
***************
*** 1753,1759 ****
* Returns OK or FAIL.
*/
static int
! channel_save(channel_T *channel, int part, char_u *buf, int len,
int prepend, char *lead)
{
readq_T *node;
--- 1761,1767 ----
* Returns OK or FAIL.
*/
static int
! channel_save(channel_T *channel, ch_part_T part, char_u *buf, int len,
int prepend, char *lead)
{
readq_T *node;
***************
*** 1828,1834 ****
channel_fill(js_read_T *reader)
{
channel_T *channel = (channel_T *)reader->js_cookie;
! int part = reader->js_cookie_arg;
char_u *next = channel_get(channel, part);
int unused;
int len;
--- 1836,1842 ----
channel_fill(js_read_T *reader)
{
channel_T *channel = (channel_T *)reader->js_cookie;
! ch_part_T part = reader->js_cookie_arg;
char_u *next = channel_get(channel, part);
int unused;
int len;
***************
*** 1866,1872 ****
* Return TRUE if there is more to read.
*/
static int
! channel_parse_json(channel_T *channel, int part)
{
js_read_T reader;
typval_T listtv;
--- 1874,1880 ----
* Return TRUE if there is more to read.
*/
static int
! channel_parse_json(channel_T *channel, ch_part_T part)
{
js_read_T reader;
typval_T listtv;
***************
*** 2046,2052 ****
* Return FAIL otherwise.
*/
static int
! channel_get_json(channel_T *channel, int part, int id, typval_T **rettv)
{
jsonq_T *head = &channel->ch_part[part].ch_json_head;
jsonq_T *item = head->jq_next;
--- 2054,2060 ----
* Return FAIL otherwise.
*/
static int
! channel_get_json(channel_T *channel, ch_part_T part, int id, typval_T **rettv)
{
jsonq_T *head = &channel->ch_part[part].ch_json_head;
jsonq_T *item = head->jq_next;
***************
*** 2080,2086 ****
* "argv[1]" etc. have further arguments, type is VAR_UNKNOWN if missing.
*/
static void
! channel_exe_cmd(channel_T *channel, int part, typval_T *argv)
{
char_u *cmd = argv[0].vval.v_string;
char_u *arg;
--- 2088,2094 ----
* "argv[1]" etc. have further arguments, type is VAR_UNKNOWN if missing.
*/
static void
! channel_exe_cmd(channel_T *channel, ch_part_T part, typval_T *argv)
{
char_u *cmd = argv[0].vval.v_string;
char_u *arg;
***************
*** 2237,2243 ****
}
static void
! append_to_buffer(buf_T *buffer, char_u *msg, channel_T *channel, int part)
{
buf_T *save_curbuf = curbuf;
linenr_T lnum = buffer->b_ml.ml_line_count;
--- 2245,2251 ----
}
static void
! append_to_buffer(buf_T *buffer, char_u *msg, channel_T *channel, ch_part_T
part)
{
buf_T *save_curbuf = curbuf;
linenr_T lnum = buffer->b_ml.ml_line_count;
***************
*** 2332,2338 ****
}
static void
! drop_messages(channel_T *channel, int part)
{
char_u *msg;
--- 2340,2346 ----
}
static void
! drop_messages(channel_T *channel, ch_part_T part)
{
char_u *msg;
***************
*** 2349,2355 ****
* Return TRUE when a message was handled, there might be another one.
*/
static int
! may_invoke_callback(channel_T *channel, int part)
{
char_u *msg = NULL;
typval_T *listtv = NULL;
--- 2357,2363 ----
* Return TRUE when a message was handled, there might be another one.
*/
static int
! may_invoke_callback(channel_T *channel, ch_part_T part)
{
char_u *msg = NULL;
typval_T *listtv = NULL;
***************
*** 2596,2602 ****
* Return TRUE if "channel" has JSON or other typeahead.
*/
static int
! channel_has_readahead(channel_T *channel, int part)
{
ch_mode_T ch_mode = channel->ch_part[part].ch_mode;
--- 2604,2610 ----
* Return TRUE if "channel" has JSON or other typeahead.
*/
static int
! channel_has_readahead(channel_T *channel, ch_part_T part)
{
ch_mode_T ch_mode = channel->ch_part[part].ch_mode;
***************
*** 2617,2623 ****
char *
channel_status(channel_T *channel, int req_part)
{
! int part;
int has_readahead = FALSE;
if (channel == NULL)
--- 2625,2631 ----
char *
channel_status(channel_T *channel, int req_part)
{
! ch_part_T part;
int has_readahead = FALSE;
if (channel == NULL)
***************
*** 2640,2646 ****
{
if (channel_is_open(channel))
return "open";
! for (part = PART_SOCK; part <= PART_ERR; ++part)
if (channel_has_readahead(channel, part))
{
has_readahead = TRUE;
--- 2648,2654 ----
{
if (channel_is_open(channel))
return "open";
! for (part = PART_SOCK; part < PART_IN; ++part)
if (channel_has_readahead(channel, part))
{
has_readahead = TRUE;
***************
*** 2654,2660 ****
}
static void
! channel_part_info(channel_T *channel, dict_T *dict, char *name, int part)
{
chanpart_T *chanpart = &channel->ch_part[part];
char namebuf[20]; /* longest is "sock_timeout" */
--- 2662,2668 ----
}
static void
! channel_part_info(channel_T *channel, dict_T *dict, char *name, ch_part_T
part)
{
chanpart_T *chanpart = &channel->ch_part[part];
char namebuf[20]; /* longest is "sock_timeout" */
***************
*** 2736,2763 ****
channel_gui_unregister(channel);
#endif
! if (channel->CH_SOCK_FD != INVALID_FD)
! {
! sock_close(channel->CH_SOCK_FD);
! channel->CH_SOCK_FD = INVALID_FD;
! }
! may_close_part(&channel->CH_IN_FD);
! may_close_part(&channel->CH_OUT_FD);
! may_close_part(&channel->CH_ERR_FD);
if (invoke_close_cb && channel->ch_close_cb != NULL)
{
typval_T argv[1];
typval_T rettv;
int dummy;
! int part;
/* Invoke callbacks before the close callback, since it's weird to
* first invoke the close callback. Increment the refcount to avoid
* the channel being freed halfway. */
++channel->ch_refcount;
ch_log(channel, "Invoking callbacks before closing");
! for (part = PART_SOCK; part <= PART_ERR; ++part)
while (may_invoke_callback(channel, part))
;
--- 2744,2767 ----
channel_gui_unregister(channel);
#endif
! ch_close_part(channel, PART_SOCK);
! ch_close_part(channel, PART_IN);
! ch_close_part(channel, PART_OUT);
! ch_close_part(channel, PART_ERR);
if (invoke_close_cb && channel->ch_close_cb != NULL)
{
typval_T argv[1];
typval_T rettv;
int dummy;
! ch_part_T part;
/* Invoke callbacks before the close callback, since it's weird to
* first invoke the close callback. Increment the refcount to avoid
* the channel being freed halfway. */
++channel->ch_refcount;
ch_log(channel, "Invoking callbacks before closing");
! for (part = PART_SOCK; part < PART_IN; ++part)
while (may_invoke_callback(channel, part))
;
***************
*** 2789,2795 ****
}
/* any remaining messages are useless now */
! for (part = PART_SOCK; part <= PART_ERR; ++part)
drop_messages(channel, part);
}
--- 2793,2799 ----
}
/* any remaining messages are useless now */
! for (part = PART_SOCK; part < PART_IN; ++part)
drop_messages(channel, part);
}
***************
*** 2802,2815 ****
void
channel_close_in(channel_T *channel)
{
! may_close_part(&channel->CH_IN_FD);
}
/*
* Clear the read buffer on "channel"/"part".
*/
static void
! channel_clear_one(channel_T *channel, int part)
{
jsonq_T *json_head = &channel->ch_part[part].ch_json_head;
cbq_T *cb_head = &channel->ch_part[part].ch_cb_head;
--- 2806,2819 ----
void
channel_close_in(channel_T *channel)
{
! ch_close_part(channel, PART_IN);
}
/*
* Clear the read buffer on "channel"/"part".
*/
static void
! channel_clear_one(channel_T *channel, ch_part_T part)
{
jsonq_T *json_head = &channel->ch_part[part].ch_json_head;
cbq_T *cb_head = &channel->ch_part[part].ch_cb_head;
***************
*** 3043,3053 ****
}
static void
! channel_close_on_error(channel_T *channel, char *func)
{
! /* Do not call emsg(), most likely the other end just exited. */
! ch_errors(channel, "%s(): Cannot read from channel, will close it soon",
! func);
/* Queue a "DETACH" netbeans message in the command queue in order to
* terminate the netbeans session later. Do not end the session here
--- 3047,3066 ----
}
static void
! ch_close_part_on_error(
! channel_T *channel, ch_part_T part, int is_err, char *func)
{
! char msgbuf[80];
!
! vim_snprintf(msgbuf, sizeof(msgbuf),
! "%%s(): Read %s from ch_part[%d], closing",
! (is_err ? "error" : "EOF"), part);
!
! if (is_err)
! /* Do not call emsg(), most likely the other end just exited. */
! ch_errors(channel, msgbuf, func);
! else
! ch_logs(channel, msgbuf, func);
/* Queue a "DETACH" netbeans message in the command queue in order to
* terminate the netbeans session later. Do not end the session here
***************
*** 3064,3084 ****
channel_save(channel, PART_SOCK, (char_u *)DETACH_MSG_RAW,
(int)STRLEN(DETACH_MSG_RAW), FALSE, "PUT ");
! /* When reading from stdout is not possible, assume the other side has
! * died. Don't close the channel right away, it may be the wrong moment
! * to invoke callbacks. */
! channel->ch_to_be_closed = TRUE;
#ifdef FEAT_GUI
/* Stop listening to GUI events right away. */
! channel_gui_unregister(channel);
#endif
}
static void
channel_close_now(channel_T *channel)
{
! ch_log(channel, "Closing channel because of previous read error");
channel_close(channel, TRUE);
if (channel->ch_nb_close_cb != NULL)
(*channel->ch_nb_close_cb)();
--- 3077,3096 ----
channel_save(channel, PART_SOCK, (char_u *)DETACH_MSG_RAW,
(int)STRLEN(DETACH_MSG_RAW), FALSE, "PUT ");
! /* When reading is not possible close this part of the channel. Don't
! * close the channel yet, there may be something to read on another part.
*/
! ch_close_part(channel, part);
#ifdef FEAT_GUI
/* Stop listening to GUI events right away. */
! channel_gui_unregister_one(channel, part);
#endif
}
static void
channel_close_now(channel_T *channel)
{
! ch_log(channel, "Closing channel because all readable fds are closed");
channel_close(channel, TRUE);
if (channel->ch_nb_close_cb != NULL)
(*channel->ch_nb_close_cb)();
***************
*** 3090,3096 ****
* The data is put in the read queue. No callbacks are invoked here.
*/
static void
! channel_read(channel_T *channel, int part, char *func)
{
static char_u *buf = NULL;
int len = 0;
--- 3102,3108 ----
* The data is put in the read queue. No callbacks are invoked here.
*/
static void
! channel_read(channel_T *channel, ch_part_T part, char *func)
{
static char_u *buf = NULL;
int len = 0;
***************
*** 3098,3111 ****
sock_T fd;
int use_socket = FALSE;
- /* If we detected a read error don't try reading again. */
- if (channel->ch_to_be_closed)
- return;
-
fd = channel->ch_part[part].ch_fd;
if (fd == INVALID_FD)
{
! ch_error(channel, "channel_read() called while socket is closed");
return;
}
use_socket = fd == channel->CH_SOCK_FD;
--- 3110,3120 ----
sock_T fd;
int use_socket = FALSE;
fd = channel->ch_part[part].ch_fd;
if (fd == INVALID_FD)
{
! ch_errors(channel, "channel_read() called while %s part is closed",
! part_names[part]);
return;
}
use_socket = fd == channel->CH_SOCK_FD;
***************
*** 3141,3147 ****
/* Reading a disconnection (readlen == 0), or an error. */
if (readlen <= 0)
! channel_close_on_error(channel, func);
#if defined(CH_HAS_GUI) && defined(FEAT_GUI_GTK)
/* signal the main loop that there is something to read */
--- 3150,3156 ----
/* Reading a disconnection (readlen == 0), or an error. */
if (readlen <= 0)
! ch_close_part_on_error(channel, part, (len < 0), func);
#if defined(CH_HAS_GUI) && defined(FEAT_GUI_GTK)
/* signal the main loop that there is something to read */
***************
*** 3157,3163 ****
* Returns NULL in case of error or timeout.
*/
char_u *
! channel_read_block(channel_T *channel, int part, int timeout)
{
char_u *buf;
char_u *msg;
--- 3166,3172 ----
* Returns NULL in case of error or timeout.
*/
char_u *
! channel_read_block(channel_T *channel, ch_part_T part, int timeout)
{
char_u *buf;
char_u *msg;
***************
*** 3237,3243 ****
int
channel_read_json_block(
channel_T *channel,
! int part,
int timeout_arg,
int id,
typval_T **rettv)
--- 3246,3252 ----
int
channel_read_json_block(
channel_T *channel,
! ch_part_T part,
int timeout_arg,
int id,
typval_T **rettv)
***************
*** 3323,3329 ****
common_channel_read(typval_T *argvars, typval_T *rettv, int raw)
{
channel_T *channel;
! int part = -1;
jobopt_T opt;
int mode;
int timeout;
--- 3332,3338 ----
common_channel_read(typval_T *argvars, typval_T *rettv, int raw)
{
channel_T *channel;
! ch_part_T part = PART_COUNT;
jobopt_T opt;
int mode;
int timeout;
***************
*** 3344,3350 ****
channel = get_channel_arg(&argvars[0], TRUE, TRUE, part);
if (channel != NULL)
{
! if (part < 0)
part = channel_part_read(channel);
mode = channel_get_mode(channel, part);
timeout = channel_get_timeout(channel, part);
--- 3353,3359 ----
channel = get_channel_arg(&argvars[0], TRUE, TRUE, part);
if (channel != NULL)
{
! if (part == PART_COUNT)
part = channel_part_read(channel);
mode = channel_get_mode(channel, part);
timeout = channel_get_timeout(channel, part);
***************
*** 3382,3391 ****
* Returns NULL when the socket isn't found.
*/
channel_T *
! channel_fd2channel(sock_T fd, int *partp)
{
channel_T *channel;
! int part;
if (fd != INVALID_FD)
for (channel = first_channel; channel != NULL;
--- 3391,3400 ----
* Returns NULL when the socket isn't found.
*/
channel_T *
! channel_fd2channel(sock_T fd, ch_part_T *partp)
{
channel_T *channel;
! ch_part_T part;
if (fd != INVALID_FD)
for (channel = first_channel; channel != NULL;
***************
*** 3411,3427 ****
channel_handle_events(void)
{
channel_T *channel;
! int part;
sock_T fd;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
{
- /* If we detected a read error don't try reading again. */
- if (channel->ch_to_be_closed)
- continue;
-
/* check the socket and pipes */
! for (part = PART_SOCK; part <= PART_ERR; ++part)
{
fd = channel->ch_part[part].ch_fd;
if (fd != INVALID_FD)
--- 3420,3432 ----
channel_handle_events(void)
{
channel_T *channel;
! ch_part_T part;
sock_T fd;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
{
/* check the socket and pipes */
! for (part = PART_SOCK; part < PART_IN; ++part)
{
fd = channel->ch_part[part].ch_fd;
if (fd != INVALID_FD)
***************
*** 3431,3437 ****
if (r == CW_READY)
channel_read(channel, part, "channel_handle_events");
else if (r == CW_ERROR)
! channel_close_on_error(channel, "channel_handle_events()");
}
}
}
--- 3436,3443 ----
if (r == CW_READY)
channel_read(channel, part, "channel_handle_events");
else if (r == CW_ERROR)
! ch_close_part_on_error(channel, part, TRUE,
! "channel_handle_events");
}
}
}
***************
*** 3444,3450 ****
* Return FAIL or OK.
*/
int
! channel_send(channel_T *channel, int part, char_u *buf, int len, char *fun)
{
int res;
sock_T fd;
--- 3450,3456 ----
* Return FAIL or OK.
*/
int
! channel_send(channel_T *channel, ch_part_T part, char_u *buf, int len, char
*fun)
{
int res;
sock_T fd;
***************
*** 3496,3502 ****
* Sets "part_read" to the read fd.
* Otherwise returns NULL.
*/
! channel_T *
send_common(
typval_T *argvars,
char_u *text,
--- 3502,3508 ----
* Sets "part_read" to the read fd.
* Otherwise returns NULL.
*/
! static channel_T *
send_common(
typval_T *argvars,
char_u *text,
***************
*** 3504,3513 ****
int eval,
jobopt_T *opt,
char *fun,
! int *part_read)
{
channel_T *channel;
! int part_send;
clear_job_options(opt);
channel = get_channel_arg(&argvars[0], TRUE, FALSE, 0);
--- 3510,3519 ----
int eval,
jobopt_T *opt,
char *fun,
! ch_part_T *part_read)
{
channel_T *channel;
! ch_part_T part_send;
clear_job_options(opt);
channel = get_channel_arg(&argvars[0], TRUE, FALSE, 0);
***************
*** 3550,3557 ****
channel_T *channel;
int id;
ch_mode_T ch_mode;
! int part_send;
! int part_read;
jobopt_T opt;
int timeout;
--- 3556,3563 ----
channel_T *channel;
int id;
ch_mode_T ch_mode;
! ch_part_T part_send;
! ch_part_T part_read;
jobopt_T opt;
int timeout;
***************
*** 3610,3616 ****
char_u buf[NUMBUFLEN];
char_u *text;
channel_T *channel;
! int part_read;
jobopt_T opt;
int timeout;
--- 3616,3622 ----
char_u buf[NUMBUFLEN];
char_u *text;
channel_T *channel;
! ch_part_T part_read;
jobopt_T opt;
int timeout;
***************
*** 3644,3650 ****
int nfd = nfd_in;
channel_T *channel;
struct pollfd *fds = fds_in;
! int part;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
{
--- 3650,3656 ----
int nfd = nfd_in;
channel_T *channel;
struct pollfd *fds = fds_in;
! ch_part_T part;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
{
***************
*** 3678,3684 ****
int ret = ret_in;
channel_T *channel;
struct pollfd *fds = fds_in;
! int part;
int idx;
chanpart_T *in_part;
--- 3684,3690 ----
int ret = ret_in;
channel_T *channel;
struct pollfd *fds = fds_in;
! ch_part_T part;
int idx;
chanpart_T *in_part;
***************
*** 3725,3731 ****
channel_T *channel;
fd_set *rfds = rfds_in;
fd_set *wfds = wfds_in;
! int part;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
{
--- 3731,3737 ----
channel_T *channel;
fd_set *rfds = rfds_in;
fd_set *wfds = wfds_in;
! ch_part_T part;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
{
***************
*** 3757,3763 ****
channel_T *channel;
fd_set *rfds = rfds_in;
fd_set *wfds = wfds_in;
! int part;
chanpart_T *in_part;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
--- 3763,3769 ----
channel_T *channel;
fd_set *rfds = rfds_in;
fd_set *wfds = wfds_in;
! ch_part_T part;
chanpart_T *in_part;
for (channel = first_channel; channel != NULL; channel = channel->ch_next)
***************
*** 3803,3809 ****
channel_T *channel = first_channel;
int ret = FALSE;
int r;
! int part = PART_SOCK;
++safe_to_invoke_callback;
--- 3809,3815 ----
channel_T *channel = first_channel;
int ret = FALSE;
int r;
! ch_part_T part = PART_SOCK;
++safe_to_invoke_callback;
***************
*** 3816,3824 ****
}
while (channel != NULL)
{
! if (channel->ch_to_be_closed)
{
! channel->ch_to_be_closed = FALSE;
channel_close_now(channel);
/* channel may have been freed, start over */
channel = first_channel;
--- 3822,3830 ----
}
while (channel != NULL)
{
! if (channel->ch_to_be_closed == 0)
{
! channel->ch_to_be_closed = (1 << PART_COUNT);
channel_close_now(channel);
/* channel may have been freed, start over */
channel = first_channel;
***************
*** 3840,3846 ****
continue;
}
if (channel->ch_part[part].ch_fd != INVALID_FD
! || channel_has_readahead(channel, part))
{
/* Increase the refcount, in case the handler causes the channel
* to be unreferenced or closed. */
--- 3846,3852 ----
continue;
}
if (channel->ch_part[part].ch_fd != INVALID_FD
! || channel_has_readahead(channel, part))
{
/* Increase the refcount, in case the handler causes the channel
* to be unreferenced or closed. */
***************
*** 3899,3905 ****
/*
* Return the "part" to write to for "channel".
*/
! int
channel_part_send(channel_T *channel)
{
if (channel->CH_SOCK_FD == INVALID_FD)
--- 3905,3911 ----
/*
* Return the "part" to write to for "channel".
*/
! ch_part_T
channel_part_send(channel_T *channel)
{
if (channel->CH_SOCK_FD == INVALID_FD)
***************
*** 3910,3916 ****
/*
* Return the default "part" to read from for "channel".
*/
! int
channel_part_read(channel_T *channel)
{
if (channel->CH_SOCK_FD == INVALID_FD)
--- 3916,3922 ----
/*
* Return the default "part" to read from for "channel".
*/
! ch_part_T
channel_part_read(channel_T *channel)
{
if (channel->CH_SOCK_FD == INVALID_FD)
***************
*** 3923,3929 ****
* If "channel" is invalid returns MODE_JSON.
*/
ch_mode_T
! channel_get_mode(channel_T *channel, int part)
{
if (channel == NULL)
return MODE_JSON;
--- 3929,3935 ----
* If "channel" is invalid returns MODE_JSON.
*/
ch_mode_T
! channel_get_mode(channel_T *channel, ch_part_T part)
{
if (channel == NULL)
return MODE_JSON;
***************
*** 3934,3940 ****
* Return the timeout of "channel"/"part"
*/
int
! channel_get_timeout(channel_T *channel, int part)
{
return channel->ch_part[part].ch_timeout;
}
--- 3940,3946 ----
* Return the timeout of "channel"/"part"
*/
int
! channel_get_timeout(channel_T *channel, ch_part_T part)
{
return channel->ch_part[part].ch_timeout;
}
***************
*** 3962,3968 ****
}
static int
! handle_io(typval_T *item, int part, jobopt_T *opt)
{
char_u *val = get_tv_string(item);
--- 3968,3974 ----
}
static int
! handle_io(typval_T *item, ch_part_T part, jobopt_T *opt)
{
char_u *val = get_tv_string(item);
***************
*** 4045,4051 ****
dict_T *dict;
int todo;
hashitem_T *hi;
! int part;
opt->jo_set = 0;
if (tv->v_type == VAR_UNKNOWN)
--- 4051,4057 ----
dict_T *dict;
int todo;
hashitem_T *hi;
! ch_part_T part;
opt->jo_set = 0;
if (tv->v_type == VAR_UNKNOWN)
***************
*** 4343,4352 ****
* Returns NULL if the handle is invalid.
* When "check_open" is TRUE check that the channel can be used.
* When "reading" is TRUE "check_open" considers typeahead useful.
! * "part" is used to check typeahead, when -1 use the default part.
*/
channel_T *
! get_channel_arg(typval_T *tv, int check_open, int reading, int part)
{
channel_T *channel = NULL;
int has_readahead = FALSE;
--- 4349,4358 ----
* Returns NULL if the handle is invalid.
* When "check_open" is TRUE check that the channel can be used.
* When "reading" is TRUE "check_open" considers typeahead useful.
! * "part" is used to check typeahead, when PART_COUNT use the default part.
*/
channel_T *
! get_channel_arg(typval_T *tv, int check_open, int reading, ch_part_T part)
{
channel_T *channel = NULL;
int has_readahead = FALSE;
***************
*** 4367,4373 ****
}
if (channel != NULL && reading)
has_readahead = channel_has_readahead(channel,
! part >= 0 ? part : channel_part_read(channel));
if (check_open && (channel == NULL || (!channel_is_open(channel)
&& !(reading && has_readahead))))
--- 4373,4379 ----
}
if (channel != NULL && reading)
has_readahead = channel_has_readahead(channel,
! part != PART_COUNT ? part : channel_part_read(channel));
if (check_open && (channel == NULL || (!channel_is_open(channel)
&& !(reading && has_readahead))))
***************
*** 4659,4665 ****
garray_T ga;
#endif
jobopt_T opt;
! int part;
job = job_alloc();
if (job == NULL)
--- 4665,4671 ----
garray_T ga;
#endif
jobopt_T opt;
! ch_part_T part;
job = job_alloc();
if (job == NULL)
***************
*** 4679,4685 ****
goto theend;
/* Check that when io is "file" that there is a file name. */
! for (part = PART_OUT; part <= PART_IN; ++part)
if ((opt.jo_set & (JO_OUT_IO << (part - PART_OUT)))
&& opt.jo_io[part] == JIO_FILE
&& (!(opt.jo_set & (JO_OUT_NAME << (part - PART_OUT)))
--- 4685,4691 ----
goto theend;
/* Check that when io is "file" that there is a file name. */
! for (part = PART_OUT; part < PART_COUNT; ++part)
if ((opt.jo_set & (JO_OUT_IO << (part - PART_OUT)))
&& opt.jo_io[part] == JIO_FILE
&& (!(opt.jo_set & (JO_OUT_NAME << (part - PART_OUT)))
*** ../vim-8.0.0026/src/eval.c 2016-09-26 22:58:54.498255420 +0200
--- src/eval.c 2016-10-09 16:54:57.401187760 +0200
***************
*** 5622,5628 ****
else if (tv->v_type == VAR_CHANNEL)
{
channel_T *ch =tv->vval.v_channel;
! int part;
typval_T dtv;
jsonq_T *jq;
cbq_T *cq;
--- 5622,5628 ----
else if (tv->v_type == VAR_CHANNEL)
{
channel_T *ch =tv->vval.v_channel;
! ch_part_T part;
typval_T dtv;
jsonq_T *jq;
cbq_T *cq;
***************
*** 5630,5636 ****
if (ch != NULL && ch->ch_copyID != copyID)
{
ch->ch_copyID = copyID;
! for (part = PART_SOCK; part <= PART_IN; ++part)
{
for (jq = ch->ch_part[part].ch_json_head.jq_next; jq != NULL;
jq = jq->jq_next)
--- 5630,5636 ----
if (ch != NULL && ch->ch_copyID != copyID)
{
ch->ch_copyID = copyID;
! for (part = PART_SOCK; part < PART_COUNT; ++part)
{
for (jq = ch->ch_part[part].ch_json_head.jq_next; jq != NULL;
jq = jq->jq_next)
*** ../vim-8.0.0026/src/structs.h 2016-09-07 22:48:11.000000000 +0200
--- src/structs.h 2016-10-09 16:39:57.571448152 +0200
***************
*** 1499,1517 ****
/* Ordering matters, it is used in for loops: IN is last, only SOCK/OUT/ERR
* are polled. */
! #define PART_SOCK 0
#define CH_SOCK_FD ch_part[PART_SOCK].ch_fd
-
#ifdef FEAT_JOB_CHANNEL
! # define INVALID_FD (-1)
!
! # define PART_OUT 1
! # define PART_ERR 2
! # define PART_IN 3
# define CH_OUT_FD ch_part[PART_OUT].ch_fd
# define CH_ERR_FD ch_part[PART_ERR].ch_fd
# define CH_IN_FD ch_part[PART_IN].ch_fd
#endif
/* The per-fd info for a channel. */
typedef struct {
--- 1499,1519 ----
/* Ordering matters, it is used in for loops: IN is last, only SOCK/OUT/ERR
* are polled. */
! typedef enum {
! PART_SOCK = 0,
#define CH_SOCK_FD ch_part[PART_SOCK].ch_fd
#ifdef FEAT_JOB_CHANNEL
! PART_OUT,
# define CH_OUT_FD ch_part[PART_OUT].ch_fd
+ PART_ERR,
# define CH_ERR_FD ch_part[PART_ERR].ch_fd
+ PART_IN,
# define CH_IN_FD ch_part[PART_IN].ch_fd
#endif
+ PART_COUNT
+ } ch_part_T;
+
+ #define INVALID_FD (-1)
/* The per-fd info for a channel. */
typedef struct {
***************
*** 1566,1579 ****
int ch_id; /* ID of the channel */
int ch_last_msg_id; /* ID of the last message */
! chanpart_T ch_part[4]; /* info for socket, out, err and in */
char *ch_hostname; /* only for socket, allocated */
int ch_port; /* only for socket */
! int ch_to_be_closed; /* When TRUE reading or writing failed
and
! * the channel must be closed when it's safe
! * to invoke callbacks. */
int ch_to_be_freed; /* When TRUE channel must be freed when
it's
* safe to invoke callbacks. */
int ch_error; /* When TRUE an error was reported.
Avoids
--- 1568,1581 ----
int ch_id; /* ID of the channel */
int ch_last_msg_id; /* ID of the last message */
! chanpart_T ch_part[PART_COUNT]; /* info for socket, out, err and
in */
char *ch_hostname; /* only for socket, allocated */
int ch_port; /* only for socket */
! int ch_to_be_closed; /* bitset of readable fds to be closed.
! * When all readable fds have been closed,
! * set to (1 << PART_COUNT). */
int ch_to_be_freed; /* When TRUE channel must be freed when
it's
* safe to invoke callbacks. */
int ch_error; /* When TRUE an error was reported.
Avoids
*** ../vim-8.0.0026/src/proto/channel.pro 2016-09-29 15:18:51.351768068
+0200
--- src/proto/channel.pro 2016-10-09 17:00:24.546924836 +0200
***************
*** 14,28 ****
void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err);
void channel_set_job(channel_T *channel, job_T *job, jobopt_T *options);
void channel_set_options(channel_T *channel, jobopt_T *opt);
! void channel_set_req_callback(channel_T *channel, int part, char_u *callback,
partial_T *partial, int id);
void channel_buffer_free(buf_T *buf);
void channel_write_any_lines(void);
void channel_write_new_lines(buf_T *buf);
! readq_T *channel_peek(channel_T *channel, int part);
char_u *channel_first_nl(readq_T *node);
! char_u *channel_get(channel_T *channel, int part);
! void channel_consume(channel_T *channel, int part, int len);
! int channel_collapse(channel_T *channel, int part, int want_nl);
int channel_can_write_to(channel_T *channel);
int channel_is_open(channel_T *channel);
char *channel_status(channel_T *channel, int req_part);
--- 14,28 ----
void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err);
void channel_set_job(channel_T *channel, job_T *job, jobopt_T *options);
void channel_set_options(channel_T *channel, jobopt_T *opt);
! void channel_set_req_callback(channel_T *channel, ch_part_T part, char_u
*callback, partial_T *partial, int id);
void channel_buffer_free(buf_T *buf);
void channel_write_any_lines(void);
void channel_write_new_lines(buf_T *buf);
! readq_T *channel_peek(channel_T *channel, ch_part_T part);
char_u *channel_first_nl(readq_T *node);
! char_u *channel_get(channel_T *channel, ch_part_T part);
! void channel_consume(channel_T *channel, ch_part_T part, int len);
! int channel_collapse(channel_T *channel, ch_part_T part, int want_nl);
int channel_can_write_to(channel_T *channel);
int channel_is_open(channel_T *channel);
char *channel_status(channel_T *channel, int req_part);
***************
*** 31,43 ****
void channel_close_in(channel_T *channel);
void channel_clear(channel_T *channel);
void channel_free_all(void);
! char_u *channel_read_block(channel_T *channel, int part, int timeout);
! int channel_read_json_block(channel_T *channel, int part, int timeout_arg,
int id, typval_T **rettv);
void common_channel_read(typval_T *argvars, typval_T *rettv, int raw);
! channel_T *channel_fd2channel(sock_T fd, int *partp);
void channel_handle_events(void);
! int channel_send(channel_T *channel, int part, char_u *buf, int len, char
*fun);
! channel_T *send_common(typval_T *argvars, char_u *text, int id, int eval,
jobopt_T *opt, char *fun, int *part_read);
void ch_expr_common(typval_T *argvars, typval_T *rettv, int eval);
void ch_raw_common(typval_T *argvars, typval_T *rettv, int eval);
int channel_poll_setup(int nfd_in, void *fds_in);
--- 31,42 ----
void channel_close_in(channel_T *channel);
void channel_clear(channel_T *channel);
void channel_free_all(void);
! char_u *channel_read_block(channel_T *channel, ch_part_T part, int timeout);
! int channel_read_json_block(channel_T *channel, ch_part_T part, int
timeout_arg, int id, typval_T **rettv);
void common_channel_read(typval_T *argvars, typval_T *rettv, int raw);
! channel_T *channel_fd2channel(sock_T fd, ch_part_T *partp);
void channel_handle_events(void);
! int channel_send(channel_T *channel, ch_part_T part, char_u *buf, int len,
char *fun);
void ch_expr_common(typval_T *argvars, typval_T *rettv, int eval);
void ch_raw_common(typval_T *argvars, typval_T *rettv, int eval);
int channel_poll_setup(int nfd_in, void *fds_in);
***************
*** 46,59 ****
int channel_select_check(int ret_in, void *rfds_in, void *wfds_in);
int channel_parse_messages(void);
int set_ref_in_channel(int copyID);
! int channel_part_send(channel_T *channel);
! int channel_part_read(channel_T *channel);
! ch_mode_T channel_get_mode(channel_T *channel, int part);
! int channel_get_timeout(channel_T *channel, int part);
void clear_job_options(jobopt_T *opt);
void free_job_options(jobopt_T *opt);
int get_job_options(typval_T *tv, jobopt_T *opt, int supported);
! channel_T *get_channel_arg(typval_T *tv, int check_open, int reading, int
part);
void job_free_all(void);
int set_ref_in_job(int copyID);
void job_unref(job_T *job);
--- 45,58 ----
int channel_select_check(int ret_in, void *rfds_in, void *wfds_in);
int channel_parse_messages(void);
int set_ref_in_channel(int copyID);
! ch_part_T channel_part_send(channel_T *channel);
! ch_part_T channel_part_read(channel_T *channel);
! ch_mode_T channel_get_mode(channel_T *channel, ch_part_T part);
! int channel_get_timeout(channel_T *channel, ch_part_T part);
void clear_job_options(jobopt_T *opt);
void free_job_options(jobopt_T *opt);
int get_job_options(typval_T *tv, jobopt_T *opt, int supported);
! channel_T *get_channel_arg(typval_T *tv, int check_open, int reading,
ch_part_T part);
void job_free_all(void);
int set_ref_in_job(int copyID);
void job_unref(job_T *job);
*** ../vim-8.0.0026/src/testdir/test_channel.vim 2016-10-03
21:37:37.619829811 +0200
--- src/testdir/test_channel.vim 2016-10-09 16:28:59.732061278 +0200
***************
*** 1505,1510 ****
--- 1505,1527 ----
call assert_equal(3, g:linecount)
endfunc
+ func Test_read_from_terminated_job()
+ if !has('job')
+ return
+ endif
+
+ let g:linecount = 0
+ if has('win32')
+ " workaround: 'shellescape' does improper escaping double quotes
+ let arg = 'import os,sys;os.close(1);sys.stderr.write(\"test\n\")'
+ else
+ let arg = 'import os,sys;os.close(1);sys.stderr.write("test\n")'
+ endif
+ call job_start([s:python, '-c', arg], {'callback': 'MyLineCountCb'})
+ call WaitFor('1 <= g:linecount')
+ call assert_equal(1, g:linecount)
+ endfunc
+
function Ch_test_close_lambda(port)
let handle = ch_open('localhost:' . a:port, s:chopt)
if ch_status(handle) == "fail"
*** ../vim-8.0.0026/src/version.c 2016-10-09 16:10:02.135942266 +0200
--- src/version.c 2016-10-09 17:01:36.282423622 +0200
***************
*** 766,767 ****
--- 766,769 ----
{ /* Add new patch number below this line */
+ /**/
+ 27,
/**/
--
[Autumn changed into Winter ... Winter changed into Spring ... Spring
changed back into Autumn and Autumn gave Winter and Spring a miss and
went straight on into Summer ... Until one day ...]
"Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD
/// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.