mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	input.c: Process only safe events before blocking.
Introduce multiqueue_process_priority() to process only events at or above a certain priority.
This commit is contained in:
		@@ -714,7 +714,6 @@ Dictionary nvim_get_mode(void)
 | 
				
			|||||||
  Dictionary rv = ARRAY_DICT_INIT;
 | 
					  Dictionary rv = ARRAY_DICT_INIT;
 | 
				
			||||||
  char *modestr = get_mode();
 | 
					  char *modestr = get_mode();
 | 
				
			||||||
  bool blocked = input_blocking();
 | 
					  bool blocked = input_blocking();
 | 
				
			||||||
  ILOG("blocked=%d", blocked);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr)));
 | 
					  PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr)));
 | 
				
			||||||
  PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
 | 
					  PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define EVENT_HANDLER_MAX_ARGC 6
 | 
					#define EVENT_HANDLER_MAX_ARGC 6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					  kEvPriorityNormal = 1,
 | 
				
			||||||
 | 
					  kEvPriorityAsync  = 2,   // safe to run in any state
 | 
				
			||||||
 | 
					} EventPriority;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef void (*argv_callback)(void **argv);
 | 
					typedef void (*argv_callback)(void **argv);
 | 
				
			||||||
typedef struct message {
 | 
					typedef struct message {
 | 
				
			||||||
  int priority;
 | 
					  int priority;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -127,6 +127,7 @@ void multiqueue_free(MultiQueue *this)
 | 
				
			|||||||
  xfree(this);
 | 
					  xfree(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Removes the next item and returns its Event.
 | 
				
			||||||
Event multiqueue_get(MultiQueue *this)
 | 
					Event multiqueue_get(MultiQueue *this)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  return multiqueue_empty(this) ? NILEVENT : multiqueue_remove(this);
 | 
					  return multiqueue_empty(this) ? NILEVENT : multiqueue_remove(this);
 | 
				
			||||||
@@ -145,45 +146,38 @@ void multiqueue_process_events(MultiQueue *this)
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  assert(this);
 | 
					  assert(this);
 | 
				
			||||||
  while (!multiqueue_empty(this)) {
 | 
					  while (!multiqueue_empty(this)) {
 | 
				
			||||||
    Event event = multiqueue_get(this);
 | 
					    Event event = multiqueue_remove(this);
 | 
				
			||||||
    if (event.handler) {
 | 
					    if (event.handler) {
 | 
				
			||||||
      event.handler(event.argv);
 | 
					      event.handler(event.argv);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void multiqueue_process_debug(MultiQueue *this)
 | 
					void multiqueue_process_priority(MultiQueue *this, int priority)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  assert(this);
 | 
					  assert(this);
 | 
				
			||||||
  QUEUE *start = QUEUE_HEAD(&this->headtail);
 | 
					  QUEUE *start = QUEUE_HEAD(&this->headtail);
 | 
				
			||||||
  QUEUE *cur   = start;
 | 
					  QUEUE *cur = start;
 | 
				
			||||||
  // MultiQueue *start = this;
 | 
					  while (!multiqueue_empty(this)) {
 | 
				
			||||||
  // MultiQueue *cur   = start;
 | 
					 | 
				
			||||||
  do {
 | 
					 | 
				
			||||||
    MultiQueueItem *item = multiqueue_node_data(cur);
 | 
					    MultiQueueItem *item = multiqueue_node_data(cur);
 | 
				
			||||||
    Event ev;
 | 
					    assert(!item->link || !this->parent);  // Only a parent queue has link-nodes
 | 
				
			||||||
    if (item->link) {
 | 
					    Event ev = multiqueueitem_get_event(item, false);
 | 
				
			||||||
      assert(!this->parent);
 | 
					
 | 
				
			||||||
      // get the next node in the linked queue
 | 
					    if (ev.priority >= priority) {
 | 
				
			||||||
      MultiQueue *linked = item->data.queue;
 | 
					      if (ev.handler) {
 | 
				
			||||||
      assert(!multiqueue_empty(linked));
 | 
					        ev.handler(ev.argv);
 | 
				
			||||||
      MultiQueueItem *child =
 | 
					      }
 | 
				
			||||||
        multiqueue_node_data(QUEUE_HEAD(&linked->headtail));
 | 
					      // Processed. Remove this item and get the new head.
 | 
				
			||||||
      ev = child->data.item.event;
 | 
					      (void)multiqueue_remove(this);
 | 
				
			||||||
 | 
					      cur = QUEUE_HEAD(&this->headtail);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      ev = item->data.item.event;
 | 
					      // Not processed. Skip this item and get the next one.
 | 
				
			||||||
 | 
					      cur = cur->next->next;
 | 
				
			||||||
 | 
					      if (!cur || cur == start) {
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
    // Event event = multiqueue_get(this);
 | 
					 | 
				
			||||||
    // if (event.handler) {
 | 
					 | 
				
			||||||
    //   event.handler(event.argv);
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ILOG("ev: priority=%d, handler=%p arg1=%s", ev.priority, ev.handler,
 | 
					 | 
				
			||||||
         ev.argv ? ev.argv[0] : "(null)");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cur = cur->next;
 | 
					 | 
				
			||||||
  } while (cur && cur != start);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Removes all events without processing them.
 | 
					/// Removes all events without processing them.
 | 
				
			||||||
@@ -213,36 +207,48 @@ size_t multiqueue_size(MultiQueue *this)
 | 
				
			|||||||
  return this->size;
 | 
					  return this->size;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Gets an Event from an item.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// @param remove   Remove the node from its queue, and free it.
 | 
				
			||||||
 | 
					static Event multiqueueitem_get_event(MultiQueueItem *item, bool remove)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  assert(item != NULL);
 | 
				
			||||||
 | 
					  Event ev;
 | 
				
			||||||
 | 
					  if (item->link) {
 | 
				
			||||||
 | 
					    // get the next node in the linked queue
 | 
				
			||||||
 | 
					    MultiQueue *linked = item->data.queue;
 | 
				
			||||||
 | 
					    assert(!multiqueue_empty(linked));
 | 
				
			||||||
 | 
					    MultiQueueItem *child =
 | 
				
			||||||
 | 
					      multiqueue_node_data(QUEUE_HEAD(&linked->headtail));
 | 
				
			||||||
 | 
					    ev = child->data.item.event;
 | 
				
			||||||
 | 
					    // remove the child node
 | 
				
			||||||
 | 
					    if (remove) {
 | 
				
			||||||
 | 
					      QUEUE_REMOVE(&child->node);
 | 
				
			||||||
 | 
					      xfree(child);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // remove the corresponding link node in the parent queue
 | 
				
			||||||
 | 
					    if (remove && item->data.item.parent_item) {
 | 
				
			||||||
 | 
					      QUEUE_REMOVE(&item->data.item.parent_item->node);
 | 
				
			||||||
 | 
					      xfree(item->data.item.parent_item);
 | 
				
			||||||
 | 
					      item->data.item.parent_item = NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ev = item->data.item.event;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return ev;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static Event multiqueue_remove(MultiQueue *this)
 | 
					static Event multiqueue_remove(MultiQueue *this)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  assert(!multiqueue_empty(this));
 | 
					  assert(!multiqueue_empty(this));
 | 
				
			||||||
  QUEUE *h = QUEUE_HEAD(&this->headtail);
 | 
					  QUEUE *h = QUEUE_HEAD(&this->headtail);
 | 
				
			||||||
  QUEUE_REMOVE(h);
 | 
					  QUEUE_REMOVE(h);
 | 
				
			||||||
  MultiQueueItem *item = multiqueue_node_data(h);
 | 
					  MultiQueueItem *item = multiqueue_node_data(h);
 | 
				
			||||||
  Event rv;
 | 
					  assert(!item->link || !this->parent);  // Only a parent queue has link-nodes
 | 
				
			||||||
 | 
					  Event ev = multiqueueitem_get_event(item, true);
 | 
				
			||||||
  if (item->link) {
 | 
					 | 
				
			||||||
    assert(!this->parent);
 | 
					 | 
				
			||||||
    // remove the next node in the linked queue
 | 
					 | 
				
			||||||
    MultiQueue *linked = item->data.queue;
 | 
					 | 
				
			||||||
    assert(!multiqueue_empty(linked));
 | 
					 | 
				
			||||||
    MultiQueueItem *child =
 | 
					 | 
				
			||||||
      multiqueue_node_data(QUEUE_HEAD(&linked->headtail));
 | 
					 | 
				
			||||||
    QUEUE_REMOVE(&child->node);
 | 
					 | 
				
			||||||
    rv = child->data.item.event;
 | 
					 | 
				
			||||||
    xfree(child);
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    if (this->parent) {
 | 
					 | 
				
			||||||
      // remove the corresponding link node in the parent queue
 | 
					 | 
				
			||||||
      QUEUE_REMOVE(&item->data.item.parent_item->node);
 | 
					 | 
				
			||||||
      xfree(item->data.item.parent_item);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    rv = item->data.item.event;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  this->size--;
 | 
					  this->size--;
 | 
				
			||||||
  xfree(item);
 | 
					  xfree(item);
 | 
				
			||||||
  return rv;
 | 
					  return ev;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void multiqueue_push(MultiQueue *this, Event event)
 | 
					static void multiqueue_push(MultiQueue *this, Event event)
 | 
				
			||||||
@@ -250,6 +256,7 @@ static void multiqueue_push(MultiQueue *this, Event event)
 | 
				
			|||||||
  MultiQueueItem *item = xmalloc(sizeof(MultiQueueItem));
 | 
					  MultiQueueItem *item = xmalloc(sizeof(MultiQueueItem));
 | 
				
			||||||
  item->link = false;
 | 
					  item->link = false;
 | 
				
			||||||
  item->data.item.event = event;
 | 
					  item->data.item.event = event;
 | 
				
			||||||
 | 
					  item->data.item.parent_item = NULL;
 | 
				
			||||||
  QUEUE_INSERT_TAIL(&this->headtail, &item->node);
 | 
					  QUEUE_INSERT_TAIL(&this->headtail, &item->node);
 | 
				
			||||||
  if (this->parent) {
 | 
					  if (this->parent) {
 | 
				
			||||||
    // push link node to the parent queue
 | 
					    // push link node to the parent queue
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ typedef struct multiqueue MultiQueue;
 | 
				
			|||||||
typedef void (*put_callback)(MultiQueue *multiq, void *data);
 | 
					typedef void (*put_callback)(MultiQueue *multiq, void *data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define multiqueue_put(q, h, ...) \
 | 
					#define multiqueue_put(q, h, ...) \
 | 
				
			||||||
  multiqueue_put_event(q, event_create(1, h, __VA_ARGS__));
 | 
					  multiqueue_put_event(q, event_create(kEvPriorityNormal, h, __VA_ARGS__));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
 | 
					#ifdef INCLUDE_GENERATED_DECLARATIONS
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -435,24 +435,26 @@ static void handle_request(Channel *channel, msgpack_object *request)
 | 
				
			|||||||
    handler.async = true;
 | 
					    handler.async = true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (handler.async) {
 | 
					  RequestEvent *evdata = xmalloc(sizeof(RequestEvent));
 | 
				
			||||||
    char buf[256] = { 0 };
 | 
					  evdata->channel = channel;
 | 
				
			||||||
    memcpy(buf, method->via.bin.ptr, MIN(255, method->via.bin.size));
 | 
					  evdata->handler = handler;
 | 
				
			||||||
    if (strcmp("nvim_get_mode", buf) == 0) {
 | 
					  evdata->args = args;
 | 
				
			||||||
      handler.async = input_blocking();
 | 
					  evdata->request_id = request_id;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  RequestEvent *event_data = xmalloc(sizeof(RequestEvent));
 | 
					 | 
				
			||||||
  event_data->channel = channel;
 | 
					 | 
				
			||||||
  event_data->handler = handler;
 | 
					 | 
				
			||||||
  event_data->args = args;
 | 
					 | 
				
			||||||
  event_data->request_id = request_id;
 | 
					 | 
				
			||||||
  incref(channel);
 | 
					  incref(channel);
 | 
				
			||||||
  if (handler.async) {
 | 
					  if (handler.async) {
 | 
				
			||||||
    on_request_event((void **)&event_data);
 | 
					    bool is_get_mode = sizeof("nvim_get_mode") - 1 == method->via.bin.size
 | 
				
			||||||
 | 
					      && !strncmp("nvim_get_mode", method->via.bin.ptr, method->via.bin.size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is_get_mode && !input_blocking()) {
 | 
				
			||||||
 | 
					      // Schedule on the main loop with special priority. #6247
 | 
				
			||||||
 | 
					      Event ev = event_create(kEvPriorityAsync, on_request_event, 1, evdata);
 | 
				
			||||||
 | 
					      multiqueue_put_event(channel->events, ev);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // Invoke immediately.
 | 
				
			||||||
 | 
					      on_request_event((void **)&evdata);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    multiqueue_put(channel->events, on_request_event, 1, event_data);
 | 
					    multiqueue_put(channel->events, on_request_event, 1, evdata);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -341,12 +341,12 @@ static bool input_poll(int ms)
 | 
				
			|||||||
    prof_inchar_enter();
 | 
					    prof_inchar_enter();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if ((ms == - 1 || ms > 0)
 | 
					  if ((ms == - 1 || ms > 0) && !events_enabled && !input_eof) {
 | 
				
			||||||
      && !(events_enabled || input_ready() || input_eof)
 | 
					    // We have discovered that the pending input will provoke a blocking wait.
 | 
				
			||||||
      ) {
 | 
					    // Process any events marked with priority `kEvPriorityAsync`: these events
 | 
				
			||||||
 | 
					    // must be handled after flushing input. See channel.c:handle_request #6247
 | 
				
			||||||
    blocking = true;
 | 
					    blocking = true;
 | 
				
			||||||
    multiqueue_process_debug(main_loop.events);
 | 
					    multiqueue_process_priority(main_loop.events, kEvPriorityAsync);
 | 
				
			||||||
    multiqueue_process_events(main_loop.events);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, input_ready() || input_eof);
 | 
					  LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, input_ready() || input_eof);
 | 
				
			||||||
  blocking = false;
 | 
					  blocking = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,6 @@ local funcs = helpers.funcs
 | 
				
			|||||||
local request = helpers.request
 | 
					local request = helpers.request
 | 
				
			||||||
local meth_pcall = helpers.meth_pcall
 | 
					local meth_pcall = helpers.meth_pcall
 | 
				
			||||||
local command = helpers.command
 | 
					local command = helpers.command
 | 
				
			||||||
local wait = helpers.wait
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('api', function()
 | 
					describe('api', function()
 | 
				
			||||||
  before_each(clear)
 | 
					  before_each(clear)
 | 
				
			||||||
@@ -222,13 +221,6 @@ describe('api', function()
 | 
				
			|||||||
    end)
 | 
					    end)
 | 
				
			||||||
  end)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  local function appendfile(fname, text)
 | 
					 | 
				
			||||||
    local file = io.open(fname, 'a')
 | 
					 | 
				
			||||||
    file:write(text)
 | 
					 | 
				
			||||||
    file:flush()
 | 
					 | 
				
			||||||
    file:close()
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('nvim_get_mode', function()
 | 
					  describe('nvim_get_mode', function()
 | 
				
			||||||
    it("during normal-mode `g` returns blocking=true", function()
 | 
					    it("during normal-mode `g` returns blocking=true", function()
 | 
				
			||||||
      nvim("input", "o")                -- add a line
 | 
					      nvim("input", "o")                -- add a line
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user