To: vim_dev@googlegroups.com Subject: Patch 7.4.1669 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 7.4.1669 Problem: When writing buffer lines to a pipe Vim may block. Solution: Avoid blocking, write more lines later. Files: src/channel.c, src/misc2.c, src/os_unix.c, src/structs.h, src/vim.h, src/proto/channel.pro, src/testdir/test_channel.vim *** ../vim-7.4.1668/src/channel.c 2016-03-28 14:42:10.367517203 +0200 --- src/channel.c 2016-03-28 19:12:44.864019212 +0200 *************** *** 973,978 **** --- 973,979 ---- /* Special mode: send last-but-one line when appending a line * to the buffer. */ in_part->ch_buffer->b_write_to_channel = TRUE; + in_part->ch_buf_append = TRUE; in_part->ch_buf_top = in_part->ch_buffer->b_ml.ml_line_count + 1; } *************** *** 1047,1052 **** --- 1048,1055 ---- channel->ch_part[PART_OUT].ch_timeout = opt->jo_out_timeout; if (opt->jo_set & JO_ERR_TIMEOUT) channel->ch_part[PART_ERR].ch_timeout = opt->jo_err_timeout; + if (opt->jo_set & JO_BLOCK_WRITE) + channel->ch_part[PART_IN].ch_block_write = 1; if (opt->jo_set & JO_CALLBACK) { *************** *** 1193,1201 **** } /* * Write any lines to the input channel. */ ! void channel_write_in(channel_T *channel) { chanpart_T *in_part = &channel->ch_part[PART_IN]; --- 1196,1273 ---- } /* + * Return TRUE if "channel" can be written to. + * Returns FALSE if the input is closed or the write would block. + */ + static int + can_write_buf_line(channel_T *channel) + { + chanpart_T *in_part = &channel->ch_part[PART_IN]; + + if (in_part->ch_fd == INVALID_FD) + return FALSE; /* pipe was closed */ + + /* for testing: block every other attempt to write */ + if (in_part->ch_block_write == 1) + in_part->ch_block_write = -1; + else if (in_part->ch_block_write == -1) + in_part->ch_block_write = 1; + + /* TODO: Win32 implementation, probably using WaitForMultipleObjects() */ + #ifndef WIN32 + { + # if defined(HAVE_SELECT) + struct timeval tval; + fd_set wfds; + int ret; + + FD_ZERO(&wfds); + FD_SET((int)in_part->ch_fd, &wfds); + tval.tv_sec = 0; + tval.tv_usec = 0; + for (;;) + { + ret = select((int)in_part->ch_fd + 1, NULL, &wfds, NULL, &tval); + # ifdef EINTR + SOCK_ERRNO; + if (ret == -1 && errno == EINTR) + continue; + # endif + if (ret <= 0 || in_part->ch_block_write == 1) + { + if (ret > 0) + ch_log(channel, "FAKED Input not ready for writing"); + else + ch_log(channel, "Input not ready for writing"); + return FALSE; + } + break; + } + # else + struct pollfd fds; + + fds.fd = in_part->ch_fd; + fds.events = POLLOUT; + if (poll(&fds, 1, 0) <= 0) + { + ch_log(channel, "Input not ready for writing"); + return FALSE; + } + if (in_part->ch_block_write == 1) + { + ch_log(channel, "FAKED Input not ready for writing"); + return FALSE; + } + # endif + } + #endif + return TRUE; + } + + /* * Write any lines to the input channel. */ ! static void channel_write_in(channel_T *channel) { chanpart_T *in_part = &channel->ch_part[PART_IN]; *************** *** 1203,1210 **** buf_T *buf = in_part->ch_buffer; int written = 0; ! if (buf == NULL) ! return; if (!buf_valid(buf) || buf->b_ml.ml_mfp == NULL) { /* buffer was wiped out or unloaded */ --- 1275,1282 ---- buf_T *buf = in_part->ch_buffer; int written = 0; ! if (buf == NULL || in_part->ch_buf_append) ! return; /* no buffer or using appending */ if (!buf_valid(buf) || buf->b_ml.ml_mfp == NULL) { /* buffer was wiped out or unloaded */ *************** *** 1215,1224 **** for (lnum = in_part->ch_buf_top; lnum <= in_part->ch_buf_bot && lnum <= buf->b_ml.ml_line_count; ++lnum) { ! if (in_part->ch_fd == INVALID_FD) ! /* pipe was closed */ ! return; ! /* TODO: check if channel can be written to, do not block on write */ write_buf_line(buf, lnum, channel); ++written; } --- 1287,1294 ---- for (lnum = in_part->ch_buf_top; lnum <= in_part->ch_buf_bot && lnum <= buf->b_ml.ml_line_count; ++lnum) { ! if (!can_write_buf_line(channel)) ! break; write_buf_line(buf, lnum, channel); ++written; } *************** *** 1229,1234 **** --- 1299,1335 ---- ch_logn(channel, "written %d lines to channel", written); in_part->ch_buf_top = lnum; + if (lnum > buf->b_ml.ml_line_count) + { + /* Writing is done, no longer need the buffer. */ + in_part->ch_buffer = NULL; + ch_log(channel, "Finished writing all lines to channel"); + } + else + ch_logn(channel, "Still %d more lines to write", + buf->b_ml.ml_line_count - lnum + 1); + } + + /* + * Write any lines waiting to be written to a channel. + */ + void + channel_write_any_lines() + { + channel_T *channel; + + for (channel = first_channel; channel != NULL; channel = channel->ch_next) + { + chanpart_T *in_part = &channel->ch_part[PART_IN]; + + if (in_part->ch_buffer != NULL) + { + if (in_part->ch_buf_append) + channel_write_new_lines(in_part->ch_buffer); + else + channel_write_in(channel); + } + } } /* *************** *** 1248,1262 **** linenr_T lnum; int written = 0; ! if (in_part->ch_buffer == buf) { if (in_part->ch_fd == INVALID_FD) ! /* pipe was closed */ ! continue; found_one = TRUE; for (lnum = in_part->ch_buf_bot; lnum < buf->b_ml.ml_line_count; ++lnum) { write_buf_line(buf, lnum, channel); ++written; } --- 1349,1364 ---- linenr_T lnum; int written = 0; ! if (in_part->ch_buffer == buf && in_part->ch_buf_append) { if (in_part->ch_fd == INVALID_FD) ! continue; /* pipe was closed */ found_one = TRUE; for (lnum = in_part->ch_buf_bot; lnum < buf->b_ml.ml_line_count; ++lnum) { + if (!can_write_buf_line(channel)) + break; write_buf_line(buf, lnum, channel); ++written; } *************** *** 1265,1270 **** --- 1367,1375 ---- ch_logn(channel, "written line %d to channel", (int)lnum - 1); else if (written > 1) ch_logn(channel, "written %d lines to channel", written); + if (lnum < buf->b_ml.ml_line_count) + ch_logn(channel, "Still %d more lines to write", + buf->b_ml.ml_line_count - lnum); in_part->ch_buf_bot = lnum; } *************** *** 2379,2384 **** --- 2484,2540 ---- /* Buffer size for reading incoming messages. */ #define MAXMSGSIZE 4096 + #if defined(HAVE_SELECT) + /* + * Add write fds where we are waiting for writing to be possible. + */ + static int + channel_fill_wfds(int maxfd_arg, fd_set *wfds) + { + int maxfd = maxfd_arg; + channel_T *ch; + + for (ch = first_channel; ch != NULL; ch = ch->ch_next) + { + chanpart_T *in_part = &ch->ch_part[PART_IN]; + + if (in_part->ch_fd != INVALID_FD && in_part->ch_buffer != NULL) + { + FD_SET((int)in_part->ch_fd, wfds); + if ((int)in_part->ch_fd >= maxfd) + maxfd = (int)in_part->ch_fd + 1; + } + } + return maxfd; + } + #else + /* + * Add write fds where we are waiting for writing to be possible. + */ + static int + channel_fill_poll_write(int nfd_in, struct pollfd *fds) + { + int nfd = nfd_in; + channel_T *ch; + + for (ch = first_channel; ch != NULL; ch = ch->ch_next) + { + chanpart_T *in_part = &ch->ch_part[PART_IN]; + + if (in_part->ch_fd != INVALID_FD && in_part->ch_buffer != NULL) + { + in_part->ch_poll_idx = nfd; + fds[nfd].fd = in_part->ch_fd; + fds[nfd].events = POLLOUT; + ++nfd; + } + else + in_part->ch_poll_idx = -1; + } + return nfd; + } + #endif + /* * Check for reading from "fd" with "timeout" msec. * Return FAIL when there is nothing to read. *************** *** 2403,2408 **** --- 2559,2568 ---- if (PeekNamedPipe((HANDLE)fd, NULL, 0, NULL, &nread, NULL) && nread > 0) return OK; + + /* perhaps write some buffer lines */ + channel_write_any_lines(); + sleep_time = deadline - GetTickCount(); if (sleep_time <= 0) break; *************** *** 2422,2452 **** #if defined(HAVE_SELECT) struct timeval tval; fd_set rfds; ! int ret; - FD_ZERO(&rfds); - FD_SET((int)fd, &rfds); tval.tv_sec = timeout / 1000; tval.tv_usec = (timeout % 1000) * 1000; for (;;) { ! ret = select((int)fd + 1, &rfds, NULL, NULL, &tval); # ifdef EINTR SOCK_ERRNO; if (ret == -1 && errno == EINTR) continue; # endif if (ret > 0) ! return OK; break; } #else ! struct pollfd fds; ! fds.fd = fd; ! fds.events = POLLIN; ! if (poll(&fds, 1, timeout) > 0) ! return OK; #endif } return FAIL; --- 2582,2637 ---- #if defined(HAVE_SELECT) struct timeval tval; fd_set rfds; ! fd_set wfds; ! int ret; ! int maxfd; tval.tv_sec = timeout / 1000; tval.tv_usec = (timeout % 1000) * 1000; for (;;) { ! FD_ZERO(&rfds); ! FD_SET((int)fd, &rfds); ! ! /* Write lines to a pipe when a pipe can be written to. Need to ! * set this every time, some buffers may be done. */ ! maxfd = (int)fd + 1; ! FD_ZERO(&wfds); ! maxfd = channel_fill_wfds(maxfd, &wfds); ! ! ret = select(maxfd, &rfds, &wfds, NULL, &tval); # ifdef EINTR SOCK_ERRNO; if (ret == -1 && errno == EINTR) continue; # endif if (ret > 0) ! { ! if (FD_ISSET(fd, &rfds)) ! return OK; ! channel_write_any_lines(); ! continue; ! } break; } #else ! for (;;) ! { ! struct pollfd fds[MAX_OPEN_CHANNELS + 1]; ! int nfd = 1; ! fds[0].fd = fd; ! fds[0].events = POLLIN; ! nfd = channel_fill_poll_write(nfd, fds); ! if (poll(fds, nfd, timeout) > 0) ! { ! if (fds[0].revents & POLLIN) ! return OK; ! channel_write_any_lines(); ! continue; ! } ! break; ! } #endif } return FAIL; *************** *** 3010,3019 **** { for (part = PART_SOCK; part < PART_IN; ++part) { ! if (channel->ch_part[part].ch_fd != INVALID_FD) { ! channel->ch_part[part].ch_poll_idx = nfd; ! fds[nfd].fd = channel->ch_part[part].ch_fd; fds[nfd].events = POLLIN; nfd++; } --- 3195,3206 ---- { for (part = PART_SOCK; part < PART_IN; ++part) { ! chanpart_T *ch_part = &channel->ch_part[part]; ! ! if (ch_part->ch_fd != INVALID_FD) { ! ch_part->ch_poll_idx = nfd; ! fds[nfd].fd = ch_part->ch_fd; fds[nfd].events = POLLIN; nfd++; } *************** *** 3022,3027 **** --- 3209,3216 ---- } } + nfd = channel_fill_poll_write(nfd, fds); + return nfd; } *************** *** 3035,3053 **** channel_T *channel; struct pollfd *fds = fds_in; int part; for (channel = first_channel; channel != NULL; channel = channel->ch_next) { for (part = PART_SOCK; part < PART_IN; ++part) { ! int idx = channel->ch_part[part].ch_poll_idx; ! if (ret > 0 && idx != -1 && fds[idx].revents & POLLIN) { channel_read(channel, part, "channel_poll_check"); --ret; } } } return ret; --- 3224,3258 ---- channel_T *channel; struct pollfd *fds = fds_in; int part; + int idx; + chanpart_T *in_part; for (channel = first_channel; channel != NULL; channel = channel->ch_next) { for (part = PART_SOCK; part < PART_IN; ++part) { ! idx = channel->ch_part[part].ch_poll_idx; ! if (ret > 0 && idx != -1 && (fds[idx].revents & POLLIN)) { channel_read(channel, part, "channel_poll_check"); --ret; } } + + in_part = &channel->ch_part[PART_IN]; + idx = in_part->ch_poll_idx; + if (ret > 0 && idx != -1 && (fds[idx].revents & POLLOUT)) + { + if (in_part->ch_buf_append) + { + if (in_part->ch_buffer != NULL) + channel_write_new_lines(in_part->ch_buffer); + } + else + channel_write_in(channel); + --ret; + } } return ret; *************** *** 3056,3069 **** # if (!defined(WIN32) && defined(HAVE_SELECT)) || defined(PROTO) /* ! * The type of "rfds" is hidden to avoid problems with the function proto. */ int ! channel_select_setup(int maxfd_in, void *rfds_in) { int maxfd = maxfd_in; channel_T *channel; fd_set *rfds = rfds_in; int part; for (channel = first_channel; channel != NULL; channel = channel->ch_next) --- 3261,3275 ---- # if (!defined(WIN32) && defined(HAVE_SELECT)) || defined(PROTO) /* ! * The "fd_set" type is hidden to avoid problems with the function proto. */ int ! channel_select_setup(int maxfd_in, void *rfds_in, void *wfds_in) { int maxfd = maxfd_in; 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) *************** *** 3081,3099 **** } } return maxfd; } /* ! * The type of "rfds" is hidden to avoid problems with the function proto. */ int ! channel_select_check(int ret_in, void *rfds_in) { int ret = ret_in; channel_T *channel; fd_set *rfds = rfds_in; int part; for (channel = first_channel; channel != NULL; channel = channel->ch_next) { --- 3287,3309 ---- } } + maxfd = channel_fill_wfds(maxfd, wfds); + return maxfd; } /* ! * The "fd_set" type is hidden to avoid problems with the function proto. */ int ! channel_select_check(int ret_in, void *rfds_in, void *wfds_in) { int ret = ret_in; 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) { *************** *** 3107,3112 **** --- 3317,3336 ---- --ret; } } + + in_part = &channel->ch_part[PART_IN]; + if (ret > 0 && in_part->ch_fd != INVALID_FD + && FD_ISSET(in_part->ch_fd, wfds)) + { + if (in_part->ch_buf_append) + { + if (in_part->ch_buffer != NULL) + channel_write_new_lines(in_part->ch_buffer); + } + else + channel_write_in(channel); + --ret; + } } return ret; *************** *** 3608,3613 **** --- 3832,3844 ---- return FAIL; } } + else if (STRCMP(hi->hi_key, "block_write") == 0) + { + if (!(supported & JO_BLOCK_WRITE)) + break; + opt->jo_set |= JO_BLOCK_WRITE; + opt->jo_block_write = get_tv_number(item); + } else break; --todo; *************** *** 3827,3834 **** clear_job_options(&opt); opt.jo_mode = MODE_NL; if (get_job_options(&argvars[1], &opt, ! JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL ! + JO_STOPONEXIT + JO_EXIT_CB + JO_OUT_IO) == FAIL) return job; /* Check that when io is "file" that there is a file name. */ --- 4058,4065 ---- clear_job_options(&opt); opt.jo_mode = MODE_NL; if (get_job_options(&argvars[1], &opt, ! JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + JO_STOPONEXIT ! + JO_EXIT_CB + JO_OUT_IO + JO_BLOCK_WRITE) == FAIL) return job; /* Check that when io is "file" that there is a file name. */ *** ../vim-7.4.1668/src/misc2.c 2016-03-19 22:11:47.424674965 +0100 --- src/misc2.c 2016-03-28 15:27:10.859834188 +0200 *************** *** 6230,6235 **** --- 6230,6238 ---- netbeans_parse_messages(); # endif # ifdef FEAT_JOB_CHANNEL + /* Write any buffer lines still to be written. */ + channel_write_any_lines(); + /* Process the messages queued on channels. */ channel_parse_messages(); # endif *** ../vim-7.4.1668/src/os_unix.c 2016-03-26 21:04:44.068416801 +0100 --- src/os_unix.c 2016-03-28 18:20:06.912604341 +0200 *************** *** 5539,5545 **** # endif #endif #ifndef HAVE_SELECT ! struct pollfd fds[6 + MAX_OPEN_CHANNELS]; int nfd; # ifdef FEAT_XCLIPBOARD int xterm_idx = -1; --- 5539,5546 ---- # endif #endif #ifndef HAVE_SELECT ! /* each channel may use in, out and err */ ! struct pollfd fds[6 + 3 * MAX_OPEN_CHANNELS]; int nfd; # ifdef FEAT_XCLIPBOARD int xterm_idx = -1; *************** *** 5652,5658 **** struct timeval tv; struct timeval *tvp; ! fd_set rfds, efds; int maxfd; long towait = msec; --- 5653,5659 ---- struct timeval tv; struct timeval *tvp; ! fd_set rfds, wfds, efds; int maxfd; long towait = msec; *************** *** 5685,5690 **** --- 5686,5692 ---- */ select_eintr: FD_ZERO(&rfds); + FD_ZERO(&wfds); FD_ZERO(&efds); FD_SET(fd, &rfds); # if !defined(__QNX__) && !defined(__CYGWIN32__) *************** *** 5725,5734 **** } # endif # ifdef FEAT_JOB_CHANNEL ! maxfd = channel_select_setup(maxfd, &rfds); # endif ! ret = select(maxfd + 1, &rfds, NULL, &efds, tvp); result = ret > 0 && FD_ISSET(fd, &rfds); if (result) --ret; --- 5727,5736 ---- } # endif # ifdef FEAT_JOB_CHANNEL ! maxfd = channel_select_setup(maxfd, &rfds, &wfds); # endif ! ret = select(maxfd + 1, &rfds, &wfds, &efds, tvp); result = ret > 0 && FD_ISSET(fd, &rfds); if (result) --ret; *************** *** 5810,5816 **** # endif #ifdef FEAT_JOB_CHANNEL if (ret > 0) ! ret = channel_select_check(ret, &rfds); #endif #endif /* HAVE_SELECT */ --- 5812,5818 ---- # endif #ifdef FEAT_JOB_CHANNEL if (ret > 0) ! ret = channel_select_check(ret, &rfds, &wfds); #endif #endif /* HAVE_SELECT */ *** ../vim-7.4.1668/src/structs.h 2016-03-28 14:11:36.858303502 +0200 --- src/structs.h 2016-03-28 19:11:36.420724515 +0200 *************** *** 1383,1394 **** --- 1383,1397 ---- #else struct timeval ch_deadline; #endif + int ch_block_write; /* for testing: 0 when not used, -1 when write + * does not block, 1 simulate blocking */ cbq_T ch_cb_head; /* dummy node for per-request callbacks */ char_u *ch_callback; /* call when a msg is not handled */ partial_T *ch_partial; buf_T *ch_buffer; /* buffer to read from or write to */ + int ch_buf_append; /* write appended lines instead top-bot */ linenr_T ch_buf_top; /* next line to send */ linenr_T ch_buf_bot; /* last line to send */ } chanpart_T; *************** *** 1457,1463 **** #define JO_ERR_BUF 0x2000000 /* "err_buf" (JO_OUT_BUF << 1) */ #define JO_IN_BUF 0x4000000 /* "in_buf" (JO_OUT_BUF << 2) */ #define JO_CHANNEL 0x8000000 /* "channel" */ ! #define JO_ALL 0xfffffff #define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE) #define JO_CB_ALL \ --- 1460,1467 ---- #define JO_ERR_BUF 0x2000000 /* "err_buf" (JO_OUT_BUF << 1) */ #define JO_IN_BUF 0x4000000 /* "in_buf" (JO_OUT_BUF << 2) */ #define JO_CHANNEL 0x8000000 /* "channel" */ ! #define JO_BLOCK_WRITE 0x10000000 /* "block_write" */ ! #define JO_ALL 0x7fffffff #define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE) #define JO_CB_ALL \ *************** *** 1499,1504 **** --- 1503,1509 ---- int jo_timeout; int jo_out_timeout; int jo_err_timeout; + int jo_block_write; /* for testing only */ int jo_part; int jo_id; char_u jo_soe_buf[NUMBUFLEN]; *** ../vim-7.4.1668/src/vim.h 2016-03-26 20:59:48.107431656 +0100 --- src/vim.h 2016-03-28 18:44:08.889680277 +0200 *************** *** 493,505 **** #ifndef HAVE_SELECT # ifdef HAVE_SYS_POLL_H # include - # define HAVE_POLL # elif defined(WIN32) # define HAVE_SELECT # else # ifdef HAVE_POLL_H # include - # define HAVE_POLL # endif # endif #endif --- 493,503 ---- *** ../vim-7.4.1668/src/proto/channel.pro 2016-03-20 20:56:56.036283020 +0100 --- src/proto/channel.pro 2016-03-28 17:35:14.380756199 +0200 *************** *** 13,19 **** 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_write_in(channel_T *channel); void channel_write_new_lines(buf_T *buf); char_u *channel_get(channel_T *channel, int part); int channel_collapse(channel_T *channel, int part); --- 13,19 ---- 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_write_any_lines(void); void channel_write_new_lines(buf_T *buf); char_u *channel_get(channel_T *channel, int part); int channel_collapse(channel_T *channel, int part); *************** *** 37,44 **** void ch_raw_common(typval_T *argvars, typval_T *rettv, int eval); int channel_poll_setup(int nfd_in, void *fds_in); int channel_poll_check(int ret_in, void *fds_in); ! int channel_select_setup(int maxfd_in, void *rfds_in); ! int channel_select_check(int ret_in, void *rfds_in); int channel_parse_messages(void); int set_ref_in_channel(int copyID); int channel_part_send(channel_T *channel); --- 37,44 ---- void ch_raw_common(typval_T *argvars, typval_T *rettv, int eval); int channel_poll_setup(int nfd_in, void *fds_in); int channel_poll_check(int ret_in, void *fds_in); ! int channel_select_setup(int maxfd_in, void *rfds_in, void *wfds_in); ! 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); *** ../vim-7.4.1668/src/testdir/test_channel.vim 2016-03-27 19:13:29.618002618 +0200 --- src/testdir/test_channel.vim 2016-03-28 18:17:17.410375574 +0200 *************** *** 791,797 **** sp pipe-input call setline(1, ['echo one', 'echo two', 'echo three']) ! let options = {'in_io': 'buffer'} if a:use_name let options['in_name'] = 'pipe-input' else --- 791,797 ---- sp pipe-input call setline(1, ['echo one', 'echo two', 'echo three']) ! let options = {'in_io': 'buffer', 'block_write': 1} if a:use_name let options['in_name'] = 'pipe-input' else *************** *** 885,891 **** let job = job_start(s:python . " test_channel_pipe.py", \ {'in_io': 'buffer', 'in_name': 'pipe-input', 'in_top': 0, ! \ 'out_io': 'buffer', 'out_name': 'pipe-output'}) call assert_equal("run", job_status(job)) try exe "normal Gaecho hello\" --- 885,892 ---- let job = job_start(s:python . " test_channel_pipe.py", \ {'in_io': 'buffer', 'in_name': 'pipe-input', 'in_top': 0, ! \ 'out_io': 'buffer', 'out_name': 'pipe-output', ! \ 'block_write': 1}) call assert_equal("run", job_status(job)) try exe "normal Gaecho hello\" *************** *** 920,926 **** let job = job_start(s:python . " test_channel_pipe.py", \ {'in_io': 'buffer', 'in_name': 'pipe-io', 'in_top': 0, ! \ 'out_io': 'buffer', 'out_name': 'pipe-io'}) call assert_equal("run", job_status(job)) try exe "normal Goecho hello\" --- 921,928 ---- let job = job_start(s:python . " test_channel_pipe.py", \ {'in_io': 'buffer', 'in_name': 'pipe-io', 'in_top': 0, ! \ 'out_io': 'buffer', 'out_name': 'pipe-io', ! \ 'block_write': 1}) call assert_equal("run", job_status(job)) try exe "normal Goecho hello\" *** ../vim-7.4.1668/src/version.c 2016-03-28 14:42:10.367517203 +0200 --- src/version.c 2016-03-28 18:58:29.712826626 +0200 *************** *** 750,751 **** --- 750,753 ---- { /* Add new patch number below this line */ + /**/ + 1669, /**/ -- hundred-and-one symptoms of being an internet addict: 145. You e-mail your boss, informing him you'll be late. /// Bram Moolenaar -- Bram@Moolenaar.net -- 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 ///