Skip to content

Commit 38ea784

Browse files
committed
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.
1 parent ac2450a commit 38ea784

3 files changed

Lines changed: 77 additions & 12 deletions

File tree

src/channel.c

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2153,11 +2153,67 @@ remove_json_node(jsonq_T *head, jsonq_T *node)
21532153
vim_free(node);
21542154
}
21552155

2156+
/*
2157+
* Add "id" to the list of JSON message IDs we are waiting on.
2158+
*/
2159+
static void
2160+
channel_add_block_id(chanpart_T *chanpart, int id)
2161+
{
2162+
garray_T *gap = &chanpart->ch_block_ids;
2163+
2164+
if (gap->ga_growsize == 0)
2165+
ga_init2(gap, (int)sizeof(int), 10);
2166+
if (ga_grow(gap, 1) == OK)
2167+
{
2168+
((int *)gap->ga_data)[gap->ga_len] = id;
2169+
++gap->ga_len;
2170+
}
2171+
}
2172+
2173+
/*
2174+
* Remove "id" from the list of JSON message IDs we are waiting on.
2175+
*/
2176+
static void
2177+
channel_remove_block_id(chanpart_T *chanpart, int id)
2178+
{
2179+
garray_T *gap = &chanpart->ch_block_ids;
2180+
int i;
2181+
2182+
for (i = 0; i < gap->ga_len; ++i)
2183+
if (((int *)gap->ga_data)[i] == id)
2184+
{
2185+
--gap->ga_len;
2186+
if (i < gap->ga_len)
2187+
{
2188+
int *p = ((int *)gap->ga_data) + i;
2189+
2190+
mch_memmove(p, p + 1, (gap->ga_len - i) * sizeof(int));
2191+
}
2192+
return;
2193+
}
2194+
siemsg("INTERNAL: channel_remove_block_id: cannot find id %d", id);
2195+
}
2196+
2197+
/*
2198+
* Return TRUE if "id" is in the list of JSON message IDs we are waiting on.
2199+
*/
2200+
static int
2201+
channel_has_block_id(chanpart_T *chanpart, int id)
2202+
{
2203+
garray_T *gap = &chanpart->ch_block_ids;
2204+
int i;
2205+
2206+
for (i = 0; i < gap->ga_len; ++i)
2207+
if (((int *)gap->ga_data)[i] == id)
2208+
return TRUE;
2209+
return FALSE;
2210+
}
2211+
21562212
/*
21572213
* Get a message from the JSON queue for channel "channel".
21582214
* When "id" is positive it must match the first number in the list.
2159-
* When "id" is zero or negative jut get the first message. But not the one
2160-
* with id ch_block_id.
2215+
* When "id" is zero or negative jut get the first message. But not one
2216+
* in the ch_block_ids list.
21612217
* When "without_callback" is TRUE also get messages that were pushed back.
21622218
* Return OK when found and return the value in "rettv".
21632219
* Return FAIL otherwise.
@@ -2182,7 +2238,8 @@ channel_get_json(
21822238
&& ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id)
21832239
|| (id <= 0 && (tv->v_type != VAR_NUMBER
21842240
|| tv->vval.v_number == 0
2185-
|| tv->vval.v_number != channel->ch_part[part].ch_block_id))))
2241+
|| !channel_has_block_id(
2242+
&channel->ch_part[part], tv->vval.v_number)))))
21862243
{
21872244
*rettv = item->jq_value;
21882245
if (tv->v_type == VAR_NUMBER)
@@ -3050,6 +3107,7 @@ channel_clear_one(channel_T *channel, ch_part_T part)
30503107
}
30513108

30523109
free_callback(&ch_part->ch_callback);
3110+
ga_clear(&ch_part->ch_block_ids);
30533111

30543112
while (ch_part->ch_writeque.wq_next != NULL)
30553113
remove_from_writeque(&ch_part->ch_writeque,
@@ -3480,6 +3538,8 @@ channel_read_block(
34803538
* result in "rettv".
34813539
* When "id" is -1 accept any message;
34823540
* Blocks until the message is received or the timeout is reached.
3541+
* In corner cases this can be called recursively, that is why ch_block_ids is
3542+
* a list.
34833543
*/
34843544
static int
34853545
channel_read_json_block(
@@ -3494,17 +3554,19 @@ channel_read_json_block(
34943554
int timeout;
34953555
chanpart_T *chanpart = &channel->ch_part[part];
34963556

3497-
ch_log(channel, "Reading JSON");
3498-
if (id != -1)
3499-
chanpart->ch_block_id = id;
3557+
ch_log(channel, "Blocking read JSON for id %d", id);
3558+
if (id >= 0)
3559+
channel_add_block_id(chanpart, id);
35003560
for (;;)
35013561
{
35023562
more = channel_parse_json(channel, part);
35033563

3504-
/* search for message "id" */
3564+
// search for message "id"
35053565
if (channel_get_json(channel, part, id, TRUE, rettv) == OK)
35063566
{
3507-
chanpart->ch_block_id = 0;
3567+
if (id >= 0)
3568+
channel_remove_block_id(chanpart, id);
3569+
ch_log(channel, "Received JSON for id %d", id);
35083570
return OK;
35093571
}
35103572

@@ -3551,15 +3613,16 @@ channel_read_json_block(
35513613
if (timeout == timeout_arg)
35523614
{
35533615
if (fd != INVALID_FD)
3554-
ch_log(channel, "Timed out");
3616+
ch_log(channel, "Timed out on id %d", id);
35553617
break;
35563618
}
35573619
}
35583620
else
35593621
channel_read(channel, part, "channel_read_json_block");
35603622
}
35613623
}
3562-
chanpart->ch_block_id = 0;
3624+
if (id >= 0)
3625+
channel_remove_block_id(chanpart, id);
35633626
return FAIL;
35643627
}
35653628

src/structs.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,8 +1680,8 @@ typedef struct {
16801680

16811681
readq_T ch_head; /* header for circular raw read queue */
16821682
jsonq_T ch_json_head; /* header for circular json read queue */
1683-
int ch_block_id; /* ID that channel_read_json_block() is
1684-
waiting for */
1683+
garray_T ch_block_ids; /* list of IDs that channel_read_json_block()
1684+
is waiting for */
16851685
/* When ch_wait_len is non-zero use ch_deadline to wait for incomplete
16861686
* message to be complete. The value is the length of the incomplete
16871687
* message when the deadline was set. If it gets longer (something was

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,8 @@ static char *(features[]) =
777777

778778
static int included_patches[] =
779779
{ /* Add new patch number below this line */
780+
/**/
781+
1512,
780782
/**/
781783
1511,
782784
/**/

0 commit comments

Comments
 (0)