Skip to content

Handle SIGUSR1 signal by calling the corresponding vimscript function#1718

Closed
JacobHayes wants to merge 2 commits intovim:masterfrom
JacobHayes:user-signal-handler
Closed

Handle SIGUSR1 signal by calling the corresponding vimscript function#1718
JacobHayes wants to merge 2 commits intovim:masterfrom
JacobHayes:user-signal-handler

Conversation

@JacobHayes
Copy link

This is a stab at allowing users to add custom handlers for the SIGUSR1 signal. My use case was automatically reloading the configuration file in all open vim processes. I chose SIGUSR1 as it is commonly used for custom handlers and, unlike SIGUSR2, doesn't appear to have any known conflicts.

The PR sets the SIGUSR1 signal as 'non-deadly' and attaches a handler function that calls the vimscript function SIGUSR1_handler.

Aside from general fitness for this in core vim, I'm unsure about a few implementation details:

  • The vimscript handler's name is defined in os_unix.c above the C wrapper function. Does that better fit in a header file?
  • Would it make sense to provide a default noop handler or is the current "Unknown function" message suitable?

I appreciate any feedback!

@ZyX-I
Copy link

ZyX-I commented May 23, 2017

It would make sense to remove the handler and create a new autocommand instead. Autocomands do not give any error messages when absent, require less code for simple thing like source $MYVIMRC (e.g. autocmd SigUSR1 * :source $MYVIMRC) and allow sharing of one event between multiple plugins if needed, though for the last thing they need some agreement regarding how to determine the cause of the signal.

I would also suggest to be consistent and provide both USR1 and USR2 handlers. Also what “known conflicts” you are referring to?

Note regarding names: I can suggest one of three:

autocmd Signal USR1 :command
autocmd SigUSR 1 :command
autocmd SigUSR1 * :command

Personally in favour of the last one: it would be easier to determine whether handler needs to be installed if it would later be chosen to support other events (e.g. autocmd SigTerm, you would not want to default to “just ignore the signal” handler here). Second variant looks the worst, first is not good for trapping deadly signals.


Also you must not call anything from a signal handler, signal may arrive at any time and disturb other activities, this especially applies for non-deadly signals and this especially applies to your code because you are going to run something highly complicated like processing VimL which has lots of interactions with globals*. You need to act like SIGINT/SIGWINCH/SIGALARM: just set a flag and return. Process flag later in the event loop and wherever else you feel necessary.

And you are missing tests.

* : E.g. what if your signal arrives while Vim is in the middle of allocating a list, after l->lv_used_next was assigned, but before first_list was assigned and sourcing vimrc has allocated another lists and stored it in s:list? You will have a situation where some lists would never be garbage collected, but what is worse is that first_list->lv_used_prev->lv_used_next would not be equal to first_list and I could not say that you will not catch a crash due to this.

src/os_unix.c Outdated
# if defined(FEAT_USR_CMDS)
typval_T rettv;
call_vim_function(SIGUSR1_HANDLER_NAME, 0, NULL, FALSE, TRUE, &rettv);
clear_tv(&rettv);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't work as-is. You're not allowed to use dynamically allocate/free memory in a signal handle among plenty of other things. Signals are error prone. A signal handler should be re-entrant and merely set a volatile a variable and not much more. That volatile variable can then be checked later when dispatching events outside of the signal handler.

See: https://www.gnu.org/software/libc/manual/html_node/Nonreentrancy.html

@brammool
Copy link
Contributor

brammool commented May 23, 2017 via email

@vim-ml
Copy link

vim-ml commented May 23, 2017 via email

@JacobHayes
Copy link
Author

Wow, thank you all for the great feedback! Using events to trigger autocmds sounds perfect for this. My plan is to add a got_usr1 status variable, register a new SigUSR1 event, and trigger autocmds in main_loop().

@ZyX-I:
The SIGUSR2 conflict I was describing refers to sysmouse using that signal, as noted here. It is leveraged when FEAT_SYSMOUSE is defined, but is otherwise deadly.

@ R0b0t1:
Could you elaborate a bit more on supporting arbitrary signals? With autocmds, each signal would have to have it's own status variable, event and signal handler. Otherwise, it would not be possible to have separate handlers for different signals.

@vim-ml
Copy link

vim-ml commented May 23, 2017 via email

@grochmal
Copy link

Could you elaborate a bit more on supporting arbitrary signals? With autocmds, each signal would have to have it's own status variable, event and signal handler. Otherwise, it would not be possible to have separate handlers for different signals.

I know that I'm answering by someone else, sorry. But one interesting way to do that would be to use the siginfo structure (save it to reference later to check which signal arrived). If you use sigaction to register a signal you can set the SA_SIGINFO and your handler will get an additional argument (struct signinfo) containing a lot of information about the signal, including the signal number. For it to work I assume that:

plan is to add a got_usr1

This is the plan, but the variable could be renamed to got_monitored_signal and it will catch a number of possible singals (SIGUSR2, SIGINT, SIGTSTP, SIGTTIN, SIGTTOU, SIGPIPE are some that Vim uses and cannot be used at all). Then the main loop would check the saved siginfo structure to see which signal it got.

The bad side is that if two different signals arrive at almost the same time one of them is lost, i.e. only one signal is caught whilst others are lost. Which should not be something bad, since, by design, if two (or more) of the same signal arrive at the same time (or during a period when they're blocked) only one is delivered.

@ZyX-I
Copy link

ZyX-I commented May 23, 2017

@grochmal got_monitored_signal does not look good. Better have an array of integers, one per signal. It is OK to loose two same signals, but not two different. Also SIGINT may be used: in this case handler needs to set two different flags (got_int does not necessary mean SIGINT), signal handler will be run from main loop after whatever was running was interrupted. (And SIGUSR2 may be used as well because FEAT_SYSMOUSE is not always defined, but I’d rather suggest to not do this.)

@codecov-io
Copy link

Codecov Report

Merging #1718 into master will decrease coverage by 72.41%.
The diff coverage is 0%.

Impacted file tree graph

@@             Coverage Diff             @@
##           master    #1718       +/-   ##
===========================================
- Coverage   74.94%    2.52%   -72.42%     
===========================================
  Files          76       70        -6     
  Lines      124906   117838     -7068     
===========================================
- Hits        93609     2977    -90632     
- Misses      31297   114861    +83564
Impacted Files Coverage Δ
src/fileio.c 0.5% <ø> (-73.69%) ⬇️
src/os_unix.c 1.22% <0%> (-57.27%) ⬇️
src/main.c 10.41% <0%> (-44.72%) ⬇️
src/crypt_zip.c 0% <0%> (-100%) ⬇️
src/sha256.c 0% <0%> (-97.96%) ⬇️
src/gui_gtk_f.c 0% <0%> (-97.74%) ⬇️
src/arabic.c 0% <0%> (-96.65%) ⬇️
src/quickfix.c 0% <0%> (-91.46%) ⬇️
src/blowfish.c 0% <0%> (-89.84%) ⬇️
src/digraph.c 0% <0%> (-87.28%) ⬇️
... and 65 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2a0b06d...eb6e89e. Read the comment docs.

@JacobHayes
Copy link
Author

I've updated it to use an autocmd for SigUSR1, though I'm not sure how to proceed with the test. I believe the signal is sent, however because it is not processed until after the function is run, the test fails. Any thoughts on that or a better way to test?

Another downfall with the current implementation is a command has to be entered before the autocmd(s) will be called. In my case, I'm changing the theme and would like it to change without having to go to each session to do something. I was hoping to add extra apply_autocmd calls within do_exmode and normal_cmd, but I'm not sure if there's an appropriate loop I can do so in. I have a feeling solving that would be a step closer to solving the tests as they are, too.

@ZyX-I I like the array idea (as opposed to a shared simple value). I'm thinking an array of structs that contain the signal, event, and received. Then, in the signal handler (which will now be for multiple signals), I find the signal (similar to in the deathtrap) and set received = 1. If I understand correctly, that should be reentrant safe, as only a single write occurs and the array elements are statically placed (need to fact check this though). Then, in main_loop(), do a similar iteration over the array, calling apply_autocmd with the stored event if received == 1. Does that sound about correct?

@LemonBoy
Copy link
Contributor

it's probably better to use a bitset here: it's more flexible (and efficient for all it matters) and it'd make supporting cases like au Signal USR1,INT :command or au Signal * :command really easy.

@ZyX-I
Copy link

ZyX-I commented May 24, 2017

@LemonBoy Not bitset, never for signals. Unless there is an atomic command “set bit X to 1 at location 0x…” this is going to be “load register, 0x…; or register, mask; store 0x…, register;” and it is completely possible to receive signal after loading, but before storing which is not a problem for integer arrays (there just is one store). The assembler of volatile int bitset = 0; int main(int argc, char **argv) { bitset |= 0x4; return bitset; } compiled with -O3 both with clang and gcc shows that at least my processor does not have “modify bit X at address 0x…” command, or it is not known by clang/gcc.

And I do not think that au Signal USR1,INT is a good idea, not to mention au Signal *. You sometimes need * for debugging, but not for normal operation of plugins. And there is SigUSR1,SigINT for the first case. And I fail to see how it is going to be easier with bitset, from my experience bitsets are always harder to work with, far less flexible and specifically in this case bitsets are going to be completely irrelevant to the implementation. Maybe by “bitset” you mean something else then “integer variable which has different meaning assigned to different bits”? Do not forget that Vim is ANSI C, no std::bitset.

@brammool
Copy link
Contributor

Dealing with the signal only in main_loop() means nothing will happen when in Insert mode.
How about moving this to parse_queued_messages(), which is where various other async calls are handled? The test should then also work using getchar()

@JacobHayes JacobHayes force-pushed the user-signal-handler branch from eb6e89e to 8d06502 Compare October 2, 2019 22:30
@codecov-io
Copy link

Codecov Report

Merging #1718 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1718      +/-   ##
==========================================
+ Coverage   81.83%   81.83%   +<.01%     
==========================================
  Files         133      133              
  Lines      146551   146559       +8     
==========================================
+ Hits       119933   119940       +7     
- Misses      26618    26619       +1
Impacted Files Coverage Δ
src/autocmd.c 87.74% <ø> (ø) ⬆️
src/os_unix.c 63.58% <100%> (+0.08%) ⬆️
src/getchar.c 82.87% <100%> (+0.04%) ⬆️
src/if_xcmdsrv.c 85.99% <0%> (-0.18%) ⬇️
src/message.c 79.5% <0%> (-0.06%) ⬇️
src/terminal.c 81.77% <0%> (-0.04%) ⬇️
src/window.c 88.25% <0%> (+0.03%) ⬆️
src/gui.c 63.7% <0%> (+0.05%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1824f45...d13b103. Read the comment docs.

@JacobHayes
Copy link
Author

@brammool thanks for the parse_queued_messages tip. I rebased this work on top of master and moved the autocmd calling logic there and fixed the test.

Working in insert mode is definitely an improvement. One keypress is required for the autocmd to be called. For a color change, a second keypress is required to update the color, but if I force a redraw in the autocmd, I can avoid the second keypress. Here's the .vimrc I was testing with:

echo("sourcing vimrc")
au SigUSR1 * source $MYVIMRC | redraw
highlight Normal ctermfg=grey ctermbg=black

@brammool
Copy link
Contributor

brammool commented Oct 4, 2019 via email

@brammool brammool closed this in be5ee86 Jun 10, 2020
@JacobHayes JacobHayes deleted the user-signal-handler branch September 28, 2020 03:02
janlazo added a commit to janlazo/neovim that referenced this pull request Oct 22, 2020
Problem:    No simple way to interrupt Vim.
Solution:   Add the SigUSR1 autocommand, triggered by SIGUSR1. (Jacob Hayes,
            closes vim/vim#1718)
vim/vim@be5ee86
janlazo added a commit to janlazo/neovim that referenced this pull request Oct 22, 2020
Problem:    No simple way to interrupt Vim.
Solution:   Add the SigUSR1 autocommand, triggered by SIGUSR1. (Jacob Hayes,
            closes vim/vim#1718)
vim/vim@be5ee86
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants