neovim / pynvim

Python client and plugin host for Nvim
http://pynvim.readthedocs.io/en/latest/
Apache License 2.0
1.47k stars 118 forks source link

Synchronous call initiated through a response of an async call drops the results of the sync call. #566

Open Badhi opened 2 months ago

Badhi commented 2 months ago

I have created a sample plugin that recreates my issue below.

function M.run() vim.fn.Start(1) vim.fn.Start(2) end

function M.update(data) print(vim.fn.GetData(data)) end

return M


- python in `test_plugin/rplugin/python3/test_plugin.py`
```python
import pynvim
import logging

@pynvim.plugin
class TestPlugin(object):
    def __init__(self, nvim : pynvim.Nvim):
        logging.basicConfig(filename = 'test.log', level='DEBUG')
        self.nvim = nvim

    @pynvim.function('Start', sync=False)
    def start(self, args):
        logging.debug('start called')
        d = args[0]
        self.nvim.exec_lua("require'test_plugin'.update(...)", d, async_ = True)

    @pynvim.function('GetData', sync = True)
    def getData(self, args):
        logging.debug('getdata called')
        d = args[0]
        return d

Here, when i call lua require'test_plugin'.run() I could see both start and getData functions in python getting called twice but the return values of the getData function is never captured by the neovim

Also, if I remove one of the vim.fn.Start calls in run() function, everything works fine.

Appreciate if someone can help me with this and check if I'm not using the pynvim apis incorrectly

justinmk commented 2 months ago

but the return values of the getData function is never captured by the neovim

The code is only calling print() on the values. Maybe the printed message is not shown because of redraw? Try appending the GetData() results to a list, then check the contents of that list.

Badhi commented 2 months ago

Hi @justinmk, Thanks for the suggestion. I tried to add the results to the list and check the results with a new call and I could see the list is still empty.

But I was able to fix the issue in a different way, but not sure if it just hides the real issue or if that is the real "usage" of the API

The fix was to schedule all the work that is done in the update function to the next in the event loop by using vim.schedule like below

function M.update(data)
    vim.schedule(function()
        print(vim.fn.GetData(data))
    end)
end

I think it makes sense since we first have to give up the current call state that was initiated by rpc call to process other rpc calls?

justinmk commented 2 months ago

I could see the list is still empty.

Are you certain that start() is called in your example (debug('start called') is written to the logs)?

I think it makes sense since we first have to give up the current call state that was initiated by rpc call to process other rpc calls?

It's certainly tricky when the "call stack" is multiply-bidirectional.

Thanks for providing this test case and info. This is something we should either document, detect, or actually support.

Badhi commented 2 months ago

Are you certain that start() is called in your example (debug('start called') is written to the logs)?

Yes, following is the log output for the failing scenario (I have enabled some debug logs in pynvim as well)

DEBUG:root:start called
DEBUG:root:start called
DEBUG:pynvim.plugin.host:calling request handler for "test_plugin/rplugin/python3/test_plugin.py:function:GetData", args: "[[1]]"
DEBUG:root:getdata called
DEBUG:pynvim.plugin.host:request handler for 'test_plugin/rplugin/python3/test_plugin.py:function:GetData [[1]]' returns: 1
DEBUG:pynvim.plugin.host:calling request handler for "test_plugin/rplugin/python3/test_plugin.py:function:GetData", args: "[[2]]"
DEBUG:root:getdata called
DEBUG:pynvim.plugin.host:request handler for 'test_plugin/rplugin/python3/test_plugin.py:function:GetData [[2]]' returns: 2