jbyuki / one-small-step-for-vimkind

Debug adapter for Neovim plugins
MIT License
409 stars 11 forks source link

nvim gets spawned recursively? #37

Closed tmillr closed 1 year ago

tmillr commented 1 year ago

Sorry I haven't pinpointed the cause of this yet, but I believe it's either this plugin or nvim-dap causing it because I've only noticed this recently and only as I've been doing debug sessions over the past few days.

This just happened to me for the 2nd or 3rd time in the last 2 days, and both times I had left a debug session stay open and stopped on a breakpoint for several hours on-end (e.g. 12 hours) (including letting my computer go to sleep and waking it back up with the session still running). Anyway, I'll go to invoke a shell command and get an EAGAIN error, and when I check my processes, there will be a very large number of nvim instances eating up all 64gb of my ram (even though I'm only running 2 or 3 myself). I am not running anything that spawns nvim processes automatically afaik (unless barebones neovim is doing that itself for some reason, which I doubt). So it is therefore likely that the cause is this plugin or nvim-dap I believe. I haven't had much chance to investigate the issue because every time it happens, by the time I realize it, my terminal freezes due to being out of ram/resources/process slots etc. Maybe it has something to do with leaving a debug session open while my computer goes to sleep? Not sure.

[!NOTE] Both times I had also toggled the dap repl, used it, and left it open as well.

I am using macOS Ventura 13.5 and neovim 0.9.1. Recently I've updated my OS and installed the dap-virtual-text plugin.

I'll try to reproduce this or investigate it further when I get the chance.


The last time it happened to me, I had launched the debuggee using this sh script:

# I use `defer_fn()` because otherwise nvim freezes if `osv.launch()`
# is called without it, `osv.launch()` also freezes nvim if called anytime
# before `VimEnter` fires. Replacing the `defer_fn()` below with `schedule()`
# likewise freezes nvim it seems, but this behavior is another issue not related
# to this topic afaik.

nvim \
  -n \
  -i NONE \
  -u NONE \
  '+set rtp^=.' \
  "+autocmd VimEnter * ++once ++nested lua vim.defer_fn(function() require'osv'.launch{ port = 8086 } end, 1e3)" \
  "$@"

nvim-dap config

```lua local api = vim.api local dap = require 'dap' local wk = require 'which-key' -- DEBUGGEE CONFIGURATION -- key: filetype of current buf -- type: key of dap.adapters dap.configurations.lua = { { type = 'nlua', request = 'attach', name = 'Attach to running Neovim instance', }, } -- ADAPTER CONFIGURATION -- Configure nvim-dap to connect to or launch a debug adapter dap.adapters.nlua = function(callback, config) callback { type = 'server', host = config.host or '127.0.0.1', port = config.port or 8086, } end -- {{{ Mappings local last_action local function expand_aliases(t) local ret = {} for keys, def in pairs(t) do for _, key in ipairs(type(keys) == 'table' and keys or { keys }) do assert(ret[key] == nil, 'duplicate key/mapping: ' .. key) ret[key] = def end end return ret end local function step_over() last_action = 'n' require('dap').step_over() end wk.register( expand_aliases { name = 'DAP/Debugger', ['\\'] = { function() return last_action and '\\' .. last_action or [[lua api.nvim_err_writeln "no previous command to reference"]] end, 'Repeat last action', expr = true, noremap = false, }, [{ 'si', 'i' }] = { function() last_action = 'i' require('dap').step_into { -- steppingGranularity = nil, askForTargets = true, } end, 'Step (i)nto', }, [{ 'so', 'o' }] = { function() last_action = 'o' require('dap').step_out { -- steppingGranularity = nil, askForTargets = true, } end, 'Step (o)ut', }, n = { step_over, 'Step over/(n)ext', }, c = { function() last_action = 'c' require('dap').continue() end, '(C)ontinue', }, [''] = { function() last_action = '' require('dap').run_to_cursor() end, 'Run to cursor', }, p = { function() require('dap').pause() end, '(P)ause thread', }, -- Breakpoints [{ 'bt', 'bb' }] = { function() require('dap').toggle_breakpoint() end, '(T)oggle (b)reakpoint', }, bl = { function() require('dap').list_breakpoints(true) end, '(L)ist (b)reakpoints', }, bc = { function() require('dap').clear_breakpoints() end, '(C)lear (b)reakpoints', }, [{ 'be', 'eb' }] = { function() require('dap').set_exception_breakpoints() end, 'Set (e)xception (b)reakpoint', }, -- gt = { -- function() -- require("dap").goto_(vim.v.count ~= 0 and vim.v.count or nil) -- end, -- "Go to cursor line", -- }, h = { function() require('dap.ui.widgets').hover() end, '(H)over', }, l = { function() require('osv').launch { -- args = vim.tbl_filter( -- (function(i) -- return function(v) -- i = i + 1 -- return i ~= 1 -- and v ~= "--embed" -- and v ~= "--headless" -- end -- end)(0), -- vim.v.argv -- ), port = 8086, } end, '(L)aunch DAP Server', }, f = { function() require('dap').focus_frame() vim.cmd 'normal! zz' end, '(F)ocus/jump to curr frame', }, q = { function() require('dap').terminate() end, 'Quit/Terminate', }, -- }, }, { -- buffer = nil, expr = false, mode = 'n', noremap = true, nowait = false, prefix = '\\', silent = false, } ) wk.register({ [''] = { function() require('dap').up() end, 'Go up in stacktrace', }, [''] = { function() require('dap').down() end, 'Go down in stacktrace', }, [''] = { step_over, 'Step over' }, }, { -- buffer = nil, expr = false, mode = 'n', noremap = true, nowait = false, -- prefix = "\\", silent = false, }) -- }}} -- {{{ Signs for _, val in ipairs(vim.fn.sign_define(vim.tbl_map(function(signdef) signdef.linehl = signdef.name .. 'SignLine' signdef.numhl = signdef.name .. 'SignNr' signdef.texthl = signdef.name .. 'Sign' signdef.culhl = signdef.name .. 'SignCul' return signdef end, { -- For breakpoints (default: `B`) { name = 'DapBreakpoint' }, -- For conditional breakpoints (default: `C`) { name = 'DapBreakpointCondition' }, -- For log points (default: `L`) { name = 'DapLogPoint' }, -- To indicate where the debugee is stopped (default: `→`) { name = 'DapStopped', -- icon = "", text = '', }, -- To indicate breakpoints rejected by the debug adapter (default: R`) { name = 'DapBreakpointRejected' }, }))) do assert(val == 0, 'failed to define sign') end -- }}} local liskey = 'tmillr' dap.listeners.after.event_stopped[liskey] = function(session, ev_payload) vim.cmd 'normal! zz' end ```

nvim-dap-virtual-text config

```lua require("nvim-dap-virtual-text").setup { enabled = true, -- enable this plugin (the default) enabled_commands = true, -- create commands DapVirtualTextEnable, DapVirtualTextDisable, DapVirtualTextToggle, (DapVirtualTextForceRefresh for refreshing when debug adapter did not notify its termination) highlight_changed_variables = true, -- highlight changed values with NvimDapVirtualTextChanged, else always NvimDapVirtualText highlight_new_as_changed = false, -- highlight new variables in the same way as changed variables (if highlight_changed_variables) show_stop_reason = true, -- show stop reason when stopped for exceptions commented = false, -- prefix virtual text with comment string only_first_definition = false, -- only show virtual text at first definition (if there are multiple) all_references = true, -- show virtual text on all all references of the variable (not only definitions) clear_on_continue = true, -- clear virtual text on "continue" (might cause flickering when stepping) --- A callback that determines how a variable is displayed or whether it should be omitted --- @param variable Variable https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable --- @param buf number --- @param stackframe dap.StackFrame https://microsoft.github.io/debug-adapter-protocol/specification#Types_StackFrame --- @param node userdata tree-sitter node identified as variable definition of reference (see `:h tsnode`) --- @param options nvim_dap_virtual_text_options Current options for nvim-dap-virtual-text --- @return string|nil A text how the virtual text should be displayed or nil, if this variable shouldn't be displayed -- display_callback = function(variable, buf, stackframe, node, options) -- if options.virt_text_pos == 'inline' then -- return ' = ' .. variable.value -- else -- return variable.name .. ' = ' .. variable.value -- end -- end, -- position of virtual text, see `:h nvim_buf_set_extmark()`, default tries to inline the virtual text. Use 'eol' to set to end of line -- virt_text_pos = vim.fn.has 'nvim-0.10' == 1 and 'inline' or 'eol', virt_text_pos = 'eol', -- Experimental Features all_frames = false, -- show virtual text for all stack frames not only current. Only works for debugpy on my machine. virt_lines = false, -- show virtual lines instead of virtual text (will flicker!) -- virt_text_win_col = nil -- position the virtual text at a fixed window column (starting from the first text column) , -- e.g. 80 to position at column 80, see `:h nvim_buf_set_extmark()` } ```
jbyuki commented 1 year ago

This issue never came up so I didn't pay too much attention but looking at the sh script, I'm wondering. Isn't the osv launch recursively (infinitely) because of the autocmd which is automatically transfered as per #35 down to children processes.

A quick workaround is probably to abort any embedded instance in which the tcp server failed to launch so that children are immediatly destroyed if the port is busy for example. It might be something else though but that may be one lead. I will commit that and let's see.

tmillr commented 1 year ago

Oh true, I had forgot about that. That might be it (due to the cli args getting inherited).

jbyuki commented 1 year ago

Have you encountered the issue again after commit aa1c4a8 ?

tmillr commented 1 year ago

Sorry I've been away and haven't run this again yet. If you'd like to close this issue I'm cool with that (I could always reopen it if needed, e.g. if I come across this issue again after updating).

Thanks for you help and quick response.