Re: thread resource leaks if thread terminates with uncaught exception

Jens Thiele <[email protected]> Mon, 16 Dec 2024 18:39:50 +0100
Newsgroups gmane.lisp.scheme.gauche
Message-ID <[email protected]>
Jens Thiele <[email protected]> writes:

> Hi,
>
> I have some resource leaks problems with a long running process.
>
> I still don't have a really simple test but I am getting closer to the
> problem.
>
> Let's say we have some webserver where some requests are really
> slow. For example using makiki: server.scm:
>
> #!/bin/sh
> #| -*- mode: scheme; coding: utf-8; -*-
> exec gosh -I. -- $0 "$@"
> |#
> (use makiki)
> (use text.html-lite)
>
> (define (hello)
>   (list (html-doctype)
> 	(html:html
> 	 (html:head (html:title "hello"))
> 	 (html:body (html:p "hello")))))
>
> (define-http-handler (GET)  "/"     (^[req app] (respond/ok req (hello))))
> (define-http-handler (GET)  "/slow" (^[req app] (sys-sleep 10) (respond/ok req (hello))))
>
> (define (main args)
>   (start-http-server :port 8081)
>   0)
>
> Then we have some reverse proxy also using makiki (that will be the
> process with the leaks). It tries to fetch many urls with a timeout in
> parallel and for some reason oaccasionally there might be an error (the
> "(when (= i 100) (error "i=100"))").
>
> #!/bin/sh
> #| -*- mode: scheme; coding: utf-8; -*-
> #export GC_PRINT_STATS=1
> exec gosh -I. -- $0 "$@"
> |#
> (use makiki)
> (use text.html-lite)
> (use rfc.http)
> (use gauche.threads)
> (use file.util)
> (use runtime-compile)
>
> (compile-and-load
>  `((inline-stub
>     (define-cproc get-gc-no ()
>       (let* ((r::(struct GC_prof_stats_s)))
>         (GC_get_prof_stats (& r) (sizeof r))
>         (return (SCM_MAKE_INT (ref r gc_no)))))))
>  '(get-gc-no))
>
> ;; todo: linux specific
> (define (num-open-files) (length (directory-list "/proc/self/fd" :children? #t)))
>
> (define-http-handler (GET) "/" (^[req app]
> 				 (receive (status headers body)
> 				     (http-get "localhost:8081" "/")
> 				   (respond/ok req body))))
>
> (define-http-handler (GET) "/timeout" (^[req app]
> 					(let1 threads (map (lambda(i)
> 							     (make-thread
> 							      (lambda()
> 								;; if i comment this one it works
> 								(when (= i 100) (error "i=100"))
> 								(receive (status headers body)
> 								    (http-get "localhost:8081" "/slow")
> 								  body))))
> 							   (iota 500))

if I use "only" 50 here - it looks like there is no problem

> 					  (for-each thread-start! threads)
> 					  (let1 timeout (absolute-time 1)
> 					    (let1 body (thread-join! (car threads) timeout #f)
> 					      (for-each (lambda(t)
> 							  (guard (e
> 								  [(uncaught-exception-condition? e)
> 								   #?=(uncaught-exception-condition-reason e)]
> 								  [else #?=e])
> 								 (thread-join! t timeout #f)))
> 							(cdr threads))
> 					      (cond [(not body)
> 						     (for-each thread-terminate! threads)
> 						     (respond/ok req (list
> 								      (html-doctype)
> 								      (html:html
> 								       (html:head (html:title "timeout"))
> 								       (html:body (html:p "timeout")
> 										  (html:p (string-append (x->string (num-open-files))
> 													 " open files, "
> 													 (x->string (get-gc-no))
> 													 " gc runs"))))))]
> 						    [else
> 						     body]))))))
>
> (define (main args)
>   #?=(sys-getpid)
>   (start-http-server :port 8080)
>   0)
>
> Finally we have some client fetching http://localhost:8080/timeout
>
> maybe curl:
> $ curl -s -o - 'http://localhost:8080/timeout'
>
> What happens for me:
> first request works but reports already 505 open file descriptors (we use
> thread-terminate! => this is expected)
> second request works but already 1004 open file descriptors (the gc did
> run and should have finalized the sockets)
> third request reports internal server error because the file descriptor
> limit of 1024 was reached and everyhing starts failing
>
> Now when I remove the:
> (when (= i 100) (error "i=100"))
> everything seems to work
>
> Slightly off-topic/different topic (but this is how I ended up here):
> it looks like pmap with the fully-concurrent-mapper doesn't join all
> threads in run-map in the case where proc raised an error.
>
> Best regards
> Jens
>
> PS: 
> the runtime-compile module can be found here:
> https://raw.githubusercontent.com/karme/gauchegc/refs/heads/master/runtime-compile/runtime-compile.scm
>
>
> _______________________________________________
> Gauche-devel mailing list
> [email protected]
> https://lists.sourceforge.net/lists/listinfo/gauche-devel