# HG changeset patch # User Bram Moolenaar # Date 1560103206 -7200 # Node ID 727f8cc87a45aedbd8477ae5c11beeaae76dd204 # Parent fd131767cde706cab6995ab774977eadd1174a87 patch 8.1.1512: ch_evalexpr() hangs when used recursively commit https://github.com/vim/vim/commit/38ea784fecf7921dca83ddc75fe9cb40708521b2 Author: Bram Moolenaar Date: Sun Jun 9 19:51:58 2019 +0200 patch 8.1.1512: ch_evalexpr() hangs when used recursively Problem: ch_evalexpr() hangs when used recursively. (Paul Jolly) Solution: Change ch_block_id from a single number to a list of IDs to wait on. diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -2154,10 +2154,66 @@ remove_json_node(jsonq_T *head, jsonq_T } /* + * Add "id" to the list of JSON message IDs we are waiting on. + */ + static void +channel_add_block_id(chanpart_T *chanpart, int id) +{ + garray_T *gap = &chanpart->ch_block_ids; + + if (gap->ga_growsize == 0) + ga_init2(gap, (int)sizeof(int), 10); + if (ga_grow(gap, 1) == OK) + { + ((int *)gap->ga_data)[gap->ga_len] = id; + ++gap->ga_len; + } +} + +/* + * Remove "id" from the list of JSON message IDs we are waiting on. + */ + static void +channel_remove_block_id(chanpart_T *chanpart, int id) +{ + garray_T *gap = &chanpart->ch_block_ids; + int i; + + for (i = 0; i < gap->ga_len; ++i) + if (((int *)gap->ga_data)[i] == id) + { + --gap->ga_len; + if (i < gap->ga_len) + { + int *p = ((int *)gap->ga_data) + i; + + mch_memmove(p, p + 1, (gap->ga_len - i) * sizeof(int)); + } + return; + } + siemsg("INTERNAL: channel_remove_block_id: cannot find id %d", id); +} + +/* + * Return TRUE if "id" is in the list of JSON message IDs we are waiting on. + */ + static int +channel_has_block_id(chanpart_T *chanpart, int id) +{ + garray_T *gap = &chanpart->ch_block_ids; + int i; + + for (i = 0; i < gap->ga_len; ++i) + if (((int *)gap->ga_data)[i] == id) + return TRUE; + return FALSE; +} + +/* * Get a message from the JSON queue for channel "channel". * When "id" is positive it must match the first number in the list. - * When "id" is zero or negative jut get the first message. But not the one - * with id ch_block_id. + * When "id" is zero or negative jut get the first message. But not one + * in the ch_block_ids list. * When "without_callback" is TRUE also get messages that were pushed back. * Return OK when found and return the value in "rettv". * Return FAIL otherwise. @@ -2182,7 +2238,8 @@ channel_get_json( && ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id) || (id <= 0 && (tv->v_type != VAR_NUMBER || tv->vval.v_number == 0 - || tv->vval.v_number != channel->ch_part[part].ch_block_id)))) + || !channel_has_block_id( + &channel->ch_part[part], tv->vval.v_number))))) { *rettv = item->jq_value; if (tv->v_type == VAR_NUMBER) @@ -3050,6 +3107,7 @@ channel_clear_one(channel_T *channel, ch } free_callback(&ch_part->ch_callback); + ga_clear(&ch_part->ch_block_ids); while (ch_part->ch_writeque.wq_next != NULL) remove_from_writeque(&ch_part->ch_writeque, @@ -3480,6 +3538,8 @@ channel_read_block( * result in "rettv". * When "id" is -1 accept any message; * Blocks until the message is received or the timeout is reached. + * In corner cases this can be called recursively, that is why ch_block_ids is + * a list. */ static int channel_read_json_block( @@ -3494,17 +3554,19 @@ channel_read_json_block( int timeout; chanpart_T *chanpart = &channel->ch_part[part]; - ch_log(channel, "Reading JSON"); - if (id != -1) - chanpart->ch_block_id = id; + ch_log(channel, "Blocking read JSON for id %d", id); + if (id >= 0) + channel_add_block_id(chanpart, id); for (;;) { more = channel_parse_json(channel, part); - /* search for message "id" */ + // search for message "id" if (channel_get_json(channel, part, id, TRUE, rettv) == OK) { - chanpart->ch_block_id = 0; + if (id >= 0) + channel_remove_block_id(chanpart, id); + ch_log(channel, "Received JSON for id %d", id); return OK; } @@ -3551,7 +3613,7 @@ channel_read_json_block( if (timeout == timeout_arg) { if (fd != INVALID_FD) - ch_log(channel, "Timed out"); + ch_log(channel, "Timed out on id %d", id); break; } } @@ -3559,7 +3621,8 @@ channel_read_json_block( channel_read(channel, part, "channel_read_json_block"); } } - chanpart->ch_block_id = 0; + if (id >= 0) + channel_remove_block_id(chanpart, id); return FAIL; } diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1680,8 +1680,8 @@ typedef struct { readq_T ch_head; /* header for circular raw read queue */ jsonq_T ch_json_head; /* header for circular json read queue */ - int ch_block_id; /* ID that channel_read_json_block() is - waiting for */ + garray_T ch_block_ids; /* list of IDs that channel_read_json_block() + is waiting for */ /* When ch_wait_len is non-zero use ch_deadline to wait for incomplete * message to be complete. The value is the length of the incomplete * message when the deadline was set. If it gets longer (something was diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -778,6 +778,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1512, +/**/ 1511, /**/ 1510,