Mailing list for all users of the OCaml language and system.
 help / color / mirror / Atom feed
* yet another signal mask patch and: what's the indended semantics?
@ 1999-05-19 17:44 Joerg Czeranski
  1999-05-20  8:51 ` Xavier Leroy
  0 siblings, 1 reply; 2+ messages in thread
From: Joerg Czeranski @ 1999-05-19 17:44 UTC (permalink / raw)
  To: caml-list

Hi.

These patches are again incremental.  I think most of the signal
stuff is now working.  Stream output isn't reentrant and thus must
not be used in handlers.

Generally, what's the meaning of enter_blocking_section()/
leave_blocking_section()?  Were they intended to be only used in
reentrant C functions?

And what's the semantics of the Unix.xxx functions supposed to be?
While the Single Unix C write() function never blocks are a successful
select(), Unix.write will block until all data is written unless the
descriptor is set to non-blocking mode.

So far I assumed that the semantics should be the same for C and Caml-Unix
functions with the same name.  I didn't yet try to check which functions
are merely wrappers and which implement a higher level semantics.

These questions make me hesitate to use O'Caml "in anger" for low level
Unix programming.

BTW, there are also a few prototypes missing, e.g. the
"extern char ** cstringvect();" in
otherlibs/unix/{execv.c,execve.c,execvp.c} should be
"extern char ** cstringvect(value);" - Ansi doesn't allow mixing
of prototypes and non-prototypes for the same function.

joerch

===== begin patches =====
*** asmrun/signals.c.old	Tue May 18 23:11:51 1999
--- asmrun/signals.c	Tue May 18 23:20:18 1999
***************
*** 13,18 ****
--- 13,19 ----
  
  #include <signal.h>
  #include <stdio.h>
+ #include <string.h> /* for memcpy */
  #if defined(TARGET_power) && defined(__linux)
  #include <asm/sigcontext.h>
  #endif
***************
*** 76,81 ****
--- 77,83 ----
  static void execute_signal(int signal_number, int in_handler)
  {
    struct caml_sigblock_node sigblock_node;
+   sigset_t saved_mask;
    int i;
  
    Assert (!async_signal_mode);
***************
*** 94,105 ****
    sigblock_node.signal_number = signal_number;
    caml_sigblock_stack = &sigblock_node;
  
    sigaddset(&current_signal_mask, signal_number);
    sigprocmask(SIG_SETMASK, &current_signal_mask, NULL);
  
    callback(Field(signal_handlers, signal_number), Val_int(signal_number));
  
!   sigdelset(&current_signal_mask, signal_number);
    /* don't reset the signal mask if we're in the handler, so the handler
       doesn't nest unnecessarily */
    if (!in_handler)
--- 96,108 ----
    sigblock_node.signal_number = signal_number;
    caml_sigblock_stack = &sigblock_node;
  
+   get_current_signal_mask(&saved_mask);
    sigaddset(&current_signal_mask, signal_number);
    sigprocmask(SIG_SETMASK, &current_signal_mask, NULL);
  
    callback(Field(signal_handlers, signal_number), Val_int(signal_number));
  
!   update_signal_mask(&saved_mask);
    /* don't reset the signal mask if we're in the handler, so the handler
       doesn't nest unnecessarily */
    if (!in_handler)
***************
*** 178,194 ****
    if (async_signal_mode) {
      /* We are interrupting a C function blocked on I/O.
         Callback the Caml code immediately. */
-     struct caml_sigblock_node sigblock_node;
- 
-     sigblock_node.next = caml_sigblock_stack;
-     sigblock_node.signal_number = sig;
-     caml_sigblock_stack = &sigblock_node;
- 
      leave_blocking_section();
!     callback(Field(signal_handlers, sig), Val_int(sig));
      enter_blocking_section();
- 
-     caml_sigblock_stack = sigblock_node.next;
    } else {
      /* We can't execute the signal code immediately.
         Instead, we remember the signal and play with the allocation limit
--- 181,189 ----
    if (async_signal_mode) {
      /* We are interrupting a C function blocked on I/O.
         Callback the Caml code immediately. */
      leave_blocking_section();
!     execute_signal(sig, 1 /* in handler */);
      enter_blocking_section();
    } else {
      /* We can't execute the signal code immediately.
         Instead, we remember the signal and play with the allocation limit
*** byterun/signals.c.old	Tue May 18 14:32:56 1999
--- byterun/signals.c	Tue May 18 15:54:41 1999
***************
*** 76,81 ****
--- 76,82 ----
  static void execute_signal(int signal_number, int in_handler)
  {
    struct longjmp_buffer raise_buf, *saved_external_raise;
+   sigset_t saved_mask;
    int i;
  
    Assert (!async_signal_mode);
***************
*** 103,114 ****
        external_raise = &raise_buf;
    }
  
    sigaddset(&current_signal_mask, signal_number);
    sigprocmask(SIG_SETMASK, &current_signal_mask, NULL);
  
    callback(Field(signal_handlers, signal_number), Val_int(signal_number));
  
!   sigdelset(&current_signal_mask, signal_number);
    /* don't reset the signal mask if we're in the handler, so the handler
       doesn't nest unnecessarily */
    if (!in_handler)
--- 104,116 ----
        external_raise = &raise_buf;
    }
  
+   get_current_signal_mask(&saved_mask);
    sigaddset(&current_signal_mask, signal_number);
    sigprocmask(SIG_SETMASK, &current_signal_mask, NULL);
  
    callback(Field(signal_handlers, signal_number), Val_int(signal_number));
  
!   update_signal_mask(&saved_mask);
    /* don't reset the signal mask if we're in the handler, so the handler
       doesn't nest unnecessarily */
    if (!in_handler)
............

*** otherlibs/unix/execv.c.orig	Tue Sep  2 14:54:33 1997
--- otherlibs/unix/execv.c	Tue May 18 15:31:55 1999
***************
*** 15,21 ****
  #include <memory.h>
  #include "unixsupport.h"
  
! extern char ** cstringvect();
  
  value unix_execv(value path, value args)     /* ML */
  {
--- 15,21 ----
  #include <memory.h>
  #include "unixsupport.h"
  
! extern char ** cstringvect(value);
  
  value unix_execv(value path, value args)     /* ML */
  {
*** otherlibs/unix/execve.c.orig	Tue Sep  2 14:54:33 1997
--- otherlibs/unix/execve.c	Tue May 18 15:31:59 1999
***************
*** 15,21 ****
  #include <memory.h>
  #include "unixsupport.h"
  
! extern char ** cstringvect();
  
  value unix_execve(value path, value args, value env)     /* ML */
  {
--- 15,21 ----
  #include <memory.h>
  #include "unixsupport.h"
  
! extern char ** cstringvect(value);
  
  value unix_execve(value path, value args, value env)     /* ML */
  {
*** otherlibs/unix/execvp.c.orig	Tue Sep  2 14:54:34 1997
--- otherlibs/unix/execvp.c	Tue May 18 15:32:03 1999
***************
*** 15,21 ****
  #include <memory.h>
  #include "unixsupport.h"
  
! extern char ** cstringvect();
  extern char ** environ;
  
  value unix_execvp(value path, value args)     /* ML */
--- 15,21 ----
  #include <memory.h>
  #include "unixsupport.h"
  
! extern char ** cstringvect(value);
  extern char ** environ;
  
  value unix_execvp(value path, value args)     /* ML */
===== end patches =====




^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: yet another signal mask patch and: what's the indended semantics?
  1999-05-19 17:44 yet another signal mask patch and: what's the indended semantics? Joerg Czeranski
@ 1999-05-20  8:51 ` Xavier Leroy
  0 siblings, 0 replies; 2+ messages in thread
From: Xavier Leroy @ 1999-05-20  8:51 UTC (permalink / raw)
  To: Joerg Czeranski; +Cc: caml-list

> These patches are again incremental.  I think most of the signal
> stuff is now working.  Stream output isn't reentrant and thus must
> not be used in handlers.

I'm getting totally lost among your various patches, and so are most
of the readers of this list, I believe.

If you have time, it would be great if you could provide the
implementors (caml-light@inria.fr, not caml-list@inria.fr) with some
explanations of what problem you're trying to fix, and how you propose
to fix them.  I know, I could just read your patches, but I'm not sure
I have the time and willingness to do so.

> Generally, what's the meaning of enter_blocking_section()/
> leave_blocking_section()?

Well, you could always read the code :-), but I'll try to explain
anyway.  The intention is that a enter_blocking_section and
leave_blocking_section delimit pieces of C code that can block for
extended periods of time, but are guaranteed not to call any Caml
runtime functions (e.g. no heap allocation and no GC).  This affects
both signal handling and OS-level threads:

- Signal handling is synchronous outside of enter_b_s/leave_b_s:
pending signals are just recorded, and tested from time to time by the
Caml code so that exception handlers are called only at "safe" points.
Inside enter_b_s/leave_b_s, signals will be processed immediately
by doing a C-to-Caml callback in the signal handler.

- For OS-level threads (the systhreads package), enter_b_s releases
the Caml masterlock (the one that ensures that only one thread can
execute Caml code at a time), and leave_b_s reacquires it.  Again,
enter_b_s/leave_b_s bracket blocking C code that does not use the Caml
runtime, i.e. with which other Caml threads may run concurrently.

> Were they intended to be only used in
> reentrant C functions?

I've heard about three different definitions of "reentrant".  What is
your question exactly?

> And what's the semantics of the Unix.xxx functions supposed to be?
> While the Single Unix C write() function never blocks are a successful
> select(), Unix.write will block until all data is written unless the
> descriptor is set to non-blocking mode.
> So far I assumed that the semantics should be the same for C and Caml-Unix
> functions with the same name.  I didn't yet try to check which functions
> are merely wrappers and which implement a higher level semantics.

The Unix library was designed in pre-POSIX times (around 1991-1992),
in an environment containing a mixture of BSD Unix and System V.  Some
of the wrappers tried to hide some of the semantic differences between
the two.  Also, some features weren't there initially
(e.g. non-blocking mode) and were added later, perhaps not in a fully
consistent way.

Now that we have POSIX and Unix 98, it would certainly be worthwhile
to go through the library again and make it as close as possible to
the specs.  Volunteers are most welcome.

> These questions make me hesitate to use O'Caml "in anger" for low level
> Unix programming.

The original goal of the Unix library wasn't to give full access to
all Unix system calls in Caml, but rather to provide a reasonable
subset for medium- to high-level systems programming.

There are some truly low-level Unix programming that you can't do with
the current Unix library, e.g. ioctl(), some signal hacks, and perhaps
some non-blocking I/O stuff.  Some of those restrictions are due to
the Caml language itself (e.g. ioctl() can't be done in a type-safe
way); others, to limitations of the OCaml runtime (e.g. signal
handling is severely constrained by runtime system invariants).

One could also argue that there are POSIX features that you really
don't want to use because they are a mess and better alternative
exists (e.g. use threads, not non-blocking I/O).

- Xavier Leroy




^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~1999-05-21 18:23 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1999-05-19 17:44 yet another signal mask patch and: what's the indended semantics? Joerg Czeranski
1999-05-20  8:51 ` Xavier Leroy

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox