thread is registered for I/O on unknown file-descriptor added by sytse on Wed Sep 21 17:58:44 2016
;; Bug in chicken 4 (current git master): when thread-wait-for-i/o! ;; gets interrupted, then from within the signal handler, calls ;; thread-wait-for-i/o! again, and before the second call returns, the ;; other end of the pipe/connection waited on by the first ;; thread-wait-for-i/o! closes the connection, chicken throws an ;; error. ;; ;; To test, write to io-problem.scm; run: ;; ;; csc -O3 -d3 -C -pthread -L -pthread io-problem.scm && ./io-problem ;; ;; ...and press ^C within 5 seconds. ;; ;; XXX this test case should be 10 lines long. (declare (disable-interrupts)) (use (srfi 18) posix) #> #include#include #include static void *C_wait_a_bit(void *ptr) { int fd = *((int*) ptr); free(ptr); printf("C_wait_a_bit: sleeping for 5 seconds...\n"); sleep(5); close(fd); /* Lazy: don't even bother writing to it */ return NULL; } <# ;; Some chicken-4.11-specific evil. This is not the part that breaks ;; (the test doesn't get to it, as long as you press ^C) (define %close-or-loop (foreign-primitive ((int fd) (scheme-object loop)) "char buf[1]; int bytes_read; /* This depends on fd being non-blocking. */ while ((bytes_read = read(fd, buf, 1)) < 0 && errno == EINTR); if (bytes_read < 0 && errno == EAGAIN) { /* This part is rather radically different in Chicken 4.11 than it was in 4.10. I'm sure this would need to change fairly often (every couple of years). */ C_av[0] = loop; /* Re-use arg-vector */ /* C_av[1] is still C_k */ C_av[2] = C_fix(fd); ((C_proc)C_fast_retrieve_proc(loop))(3, C_av); } else { /* The other end is closed without writing anything */ assert(bytes_read <= 0); close(fd); C_kontinue(C_k, C_SCHEME_UNDEFINED); }")) ;; Have the current Chicken thread wait until a pthreads thread has ;; sent us some data through a pipe, and then close the file ;; descriptor of our end. This needs to be resilient against EINTR, ;; and expects fd to have non-blocking semantics. (: %wait-and-close (fixnum -> void)) (define (%wait-and-close fd) (print "%wait-and-close: " (current-thread) " waiting for " fd) (thread-wait-for-i/o! fd #:input) (%close-or-loop fd %wait-and-close)) (: make-cleanup-thread (-> fixnum)) (define (make-cleanup-thread) (let ((%make-cleanup-thread (foreign-primitive ((int fd)) "pthread_t thread; int *ptr = malloc(sizeof(int)); if (ptr == NULL) { errno = ENOMEM; return(C_SCHEME_FALSE); } *ptr = fd; C_r=C_SCHEME_FALSE; /* NB: pthread functions return (positive) error numbers on error */ if ((errno = pthread_create(&thread, NULL, &C_wait_a_bit, ptr)) != 0) close(fd); else if ((errno = pthread_detach(thread)) == 0) C_r=C_SCHEME_TRUE;"))) (receive (readfd writefd) (create-pipe) (file-control readfd fcntl/setfl open/nonblock) (or (%make-cleanup-thread writefd) (let ((err (%strerror %errno))) (##core#inline "close" readfd) (error '%make-cleanup-thread err))) readfd))) (define cleanup-fd #f) ;; Replacing ##sys#user-interrupt-hook with this version fixes the ;; problem. (define (my/user-interrupt-hook) (define (break) (##sys#signal-hook #:user-interrupt #f)) (##sys#setslot ##sys#primordial-thread 1 break)) (let ((handler (lambda (_) ;; Start a second pthreads thread, that will close a ;; file descriptor after 5 seconds (print "##sys#user-interrupt-hook from " (current-thread)) (set! cleanup-fd (make-cleanup-thread)) ;; (my/user-interrupt-hook) (##sys#user-interrupt-hook) ))) (set-signal-handler! signal/int handler) (set-signal-handler! signal/term handler)) (define (exit-handler) (when cleanup-fd (condition-case (begin (print "Exit handler: " (current-thread) " calling %wait-and-close for fd " cleanup-fd) ;; Wait using thread-wait-for-i/o! (%wait-and-close cleanup-fd)) ((user-interrupt) (exit-handler))))) (on-exit exit-handler) (let loop () ;; Make a C_wait_a_bit thread, which will close the write end of the ;; pipe pointed to by fd after 5 seconds. (let ((fd (make-cleanup-thread))) (print (current-thread) " waiting for fd " fd) (thread-wait-for-i/o! fd input:)) (loop)) ;; This doesn't work, as it really does have to be a real thread, not ;; a chicken one, that closes the file descriptor behind our back: ;; (receive (readfd writefd) (create-pipe) ;; (file-control readfd fcntl/setfl open/nonblock) ;; (thread-start! (lambda () ;; (thread-sleep! 3) ;; (file-close writefd))) ;; (thread-wait-for-i/o! readfd input:))