Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Implementing signals for Win32 Perl using named pipes

by Corion (Patriarch)
on Mar 11, 2008 at 08:56 UTC ( [id://673433]=perlquestion: print w/replies, xml ) Need Help??

Corion has asked for the wisdom of the Perl Monks concerning the following question:

On perl5-porters Nicholas Clark had the devious idea of implementing SA_SIGINFO on unixish Perls through a pipe. I'm a fan of devious ideas and I see some potential in this for Windows too.

The idea of SA_SIGINFO is, as far as I understand it, to send not only a signal from the outside to a process but also to include some more information to it (like, the filename to which to dump information etc). The problem, as always with signals is, that they come at times when it's not prudent for perl to handle them right away, so Perl needs to stash the signal and the additional information away until it is ready to handle the event. Nick's idea now is to create a pipe, write the information to the pipe when the signal arrives and once Perl is ready again to handle the signal in Perl code (PERL_ASYNC_CHECK()), to read the information back from that pipe.

On Windows, Perl basically does not support signals at all, because Windows has no signals.

It occurred to me that Perl could use named pipes on Windows to implement signals (from the outside world, or at least, other Perl processes) as well. Here is how I envision that could be done:

In principle on Win32, a named pipe is just a file with the (magic) name \\.\pipe\$name, so Perl could (upon request?) create a pipe \\.\pipe\Perl\$pid. For sending a message/signal to the process, you just copy the content to said pipe and for receiving a message from it, you just read from that pipe, in an atomic fashion of course. Perl processes could handle sending signals through magic in kill etc. and other processes can just write to the pipe if they know the proper format.

All the Win32 API is already there, even in Perl (CreateFile() and CreateNamedPipe()), but what I'm missing for this crazy idea I have no time to implement is a way to get (Perl code to run) at PERL_ASYNC_CHECK(). I presume that having a Perl handler for PERL_ASYNC_CHECK() is not possible, as PERL_ASYNC_CHECK() is part of the runloop and Perl prefers not to reenter the interpreter while it is doing housekeeping.

On the third hand, I'm not sure whether having signals is really something people want on Win32 - I haven't felt the need for it at all. The unix-like usage of signals would be limited to other Perl processes anyway due to the special signal implementation. But sending a signal to any Perl process could then also be done by COPY CON: \\.\pipe\Perl\42 from the command line or any process, provided that the format written to that "file" is something Perl will recognize.

As the crazy second stage, this could be used as the central entrypoint+queue for all things asynchronous, like asynchronous file IO and window messages, provided we are content with the idea of stuffing all this into a named pipe which will eventually get full.

Basically, I'm looking for Perl internals wisdom and Win32 wisdom to get a better roadmap of where the big roadblocks for this idea lie.

Replies are listed 'Best First'.
Re: Implementing signals for Win32 Perl using named pipes
by BrowserUk (Patriarch) on Mar 11, 2008 at 10:08 UTC

    Win32 Perl processes already have an asynchronous, interprocess, queuing mechanism. Its called the process queue.

    It is how the few signals currently implemented are done. Its great advantage over any other mechanism is that the system already delivers some signals this way. ^C, ^Break, asynchronous timers, etc. It could be extended to emulate (some) other signals. A good first step would be to not convert all but four signals, (BREAK, INT, QUIT & TERM), sent to a process, into untrappable, fatal ones.

    How far you could go with emulating signals is an open question. On *nix, many of them are raised by the system itself. For example, it would be possible to have another perl process signal its parent (SIGCHLD) when it terminates. But it's extremely difficult to arrange this for any non-perl child process.

    It might be possible to inject code into the non-perl child process, but only if the required privileges are available. In many cases they would not be, which makes for a consistency problem. Even when the privileges are available, code injection is hard to get right for arbitrary processes. And, in many cases it could be seen as a security risk that opened the door to abuse.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      Thanks - this is just the information on Windows that I hoped to get from Perlmonks! I wasn't aware that Windows has a process (message) queue. So far, I was under the impression that for a message queue you always needed a window handle.

      As for sending "signals" from non-Perl processes, I think this is a lesser priority, because any non-Perl process can always use the moral equivalent of

      system('perl','-e',"kill SIGWHATEVER => $pid")
      to send a signal to a Perl process. I think code injection is an interesting toy but far too unreliable/difficult if you want to use IPC. If the easier alternative is shared files, code injection does not look good :).

        I wasn't aware that Windows has a process (message) queue. So far, I was under the impression that for a message queue you always needed a window handle.

        Um. I get a little fuzzy with my terminology. Threads can(*) have message queues, not processes. Of course, in a single threaded process, the two are (roughly) equivalent.

        (*)From PostThreadMessage(): "The system creates a thread's message queue when the thread makes its first call to one of the User or GDI functions.".

        In Perl, this (probably) occurs when Win32_create_message_window() is called:

        ## win32.c HWND win32_create_message_window() { /* "message-only" windows have been implemented in Windows 2000 an +d later. * On earlier versions we'll continue to post messages to a specif +ic * thread and use hwnd==NULL. This is brittle when either an embe +dding * application or an XS module is also posting messages to hwnd=NU +LL * because once removed from the queue they cannot be delivered to + the * "right" place with DispatchMessage() anymore, as there is no Wi +ndowProc * if there is no window handle. */ if (!IsWin2000()) return NULL; return CreateWindow("Static", "", 0, 0, 0, 0, 0, HWND_MESSAGE, 0, +0, NULL); }

        See also win32_kill() in the same file.

        If you use the fork emulation, to start a non-perl, child process, then a thread is spawned to act as a 'placeholder' for the actual process. This then waits for the alien process to terminate and so can (could?) post (raise) a SIGCHLD to the main or spawning thread. The problem is that signals sent to the child pseudo-process' pseudo-pid do not necessarially reflect the state of, or affect, the real alien process. And that's where the emulation falls down.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
      BREAK, CTRL-C, QUIT can be handled using a Control Handler. You can setup a handler function using the Win32 API SetConsoleCtrlHandler() and raise one of these "signals" using GenerateConsoleCtrlEvent().
      When a new process is created on Windows it can inherit the console of the parent, but this can be hidden (SW_HIDE). That way it is possible to emulate sending a signal to a "process group".

      Using a named pipe has its advantages, but would also need to be linked to exception handling as well, C0000005 generate a SIGSEGV? Emulating a SIGCHLD would be difficult, since there is no way to identify your parent, unless its PID is passed explicitly.

      I agree with the comments on injection, but if anyone wants to pursue that one I have some code which uses that technique in Win32::EnvProcess.
Re: Implementing signals for Win32 Perl using named pipes
by cdarke (Prior) on Mar 11, 2008 at 12:57 UTC
    I don't wish to be a wet blanket on what, to me, seems like a neat idea (but I will anyway).

    Using the PID for the name of a pipe could give a race condition: rare, but possible. Senario:
    1. Process A wants to send a signal to Process B, so opens the pipe.(CreateFile).
    2. Process B ends.
    3. Process C starts with the same PID as Process B. PIDs can get reused at once on Windows.
    4. Process A sends the signal, but to C instead of B.

    A "linger" mechanism is required, or maybe some other identifier to be passed down the pipe? At least the process can detect if the pipe already exists before creating it, but the possibility of a race still exists in the period between Process A getting the PID and actually sending the signal.

    There are also issues to be thought through with threads, particularly considering the implementation of fork. Using a TID rather than a PID may resolve them, and make the race condition less likely, but still not impossible. Update: What would be really good is an implementation of waitid() with siginfo_t fully supported.
      PIDs can get reused at once on Windows.

      Really? On my system I cannot get a pid to be reused with anything less that 2632 intervening process starts. Indeed, it is always 2632 intervening starts on my system, which maybe indicative of some flaw in my test method. But still, I think the quoted statement is suspect also?

      #! perl -slw use strict; my %pids; for ( 1 .. 2**16 ) { print my $p = open my $cmd, "cmd /c echo $_ |"; 1 while <$cmd>; close $cmd; exists $pids{ $p } and die sprintf "Duplicate $p after %d process starts", keys % +pids; $pids{ $p }++; } __END__ Duplicate 3904 after 2632 process starts at C:\test\junk11.pl line 10.
      Using a TID rather than a PID may resolve them, and make the race condition less likely, but still not impossible.

      Thread ids are simple incrementing numbers starting from 1 in each process, and would be more (very) likely to be repeated.

      I'm not at all certain that there is a potential race condition. If the process with the pid creates its own named pipe, then when the process terminates for any reason, that named pipe will cease to exist before the process is finally cleared from memory. The system won't allow that to happen until all open handles have been destroyed. And it won't allow the reuse of a pid until the old process has been properly cleaned from the internal tables.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        That you get these symptoms with PIDs are probably a feature of the way perl is doing things, rather than Windows. PIDs and TIDs are created in the same way by kernel (they are just different flavours of the same thing).

        The pipe will only be destroyed when the last handle is closed. If another process has a handle open then the pipe will not be destroyed when the server dies. However, this does raise a simple solution to my race condition. If the client opens a handle to the server process before opening the pipe then it effectively creates a zombie, so the pid cannot be reused. Obviously tidying these handles will be important.

      I don't see that race condition as too critical because the same can happen everywhere where PIDs are recycled too soon. If we find out that PIDs get recycled far too quickly, we can think of a solution then. :)

      A pipe cannot exist at process startup (by mechanism), because every process creates \\.\pipe\Perl\$$ and $$ is unique per process. The fork() emulation and signals between fork()ed children will be interesting to solve under this umbrella, but signals beween forked children could be handled by using shared memory anyway. I'm not sure how unique the fork-child-PIDs are across process boundaries, so maybe using $$ would still work, even with the fork emulation.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://673433]
Approved by bingos
Front-paged by bingos
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (5)
As of 2024-03-19 03:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found