Commit 69cef96e authored by Kirill Smelkov's avatar Kirill Smelkov

go: Don't allow leaked goroutines to prevent program to exit

This is the Go behaviour, as demonstratd by the following program:

---- 8< ----
package main

import (
	"fmt"
	"time"
)

func work(w int) {
	for i := 0; ; i++ {
		fmt.Printf("w%d: %d\n", w, i)
		time.Sleep(1*time.Second)
	}
}

func main() {
	for i := 0; i < 100; i++ {
		go work(i)
	}

	time.Sleep(3*time.Second)
	println("main: exit")
}
---- 8< ----
parent f2905909
...@@ -78,7 +78,9 @@ class _PanicError(Exception): ...@@ -78,7 +78,9 @@ class _PanicError(Exception):
# NOTE it spawns threading.Thread, but if gevent was activated via # NOTE it spawns threading.Thread, but if gevent was activated via
# `gevent.monkey.patch_all`, it will spawn greenlet, not full OS thread. # `gevent.monkey.patch_all`, it will spawn greenlet, not full OS thread.
def go(f, *argv, **kw): def go(f, *argv, **kw):
threading.Thread(target=f, args=argv, kwargs=kw).start() t = threading.Thread(target=f, args=argv, kwargs=kw)
t.daemon = True # leaked goroutines don't prevent program to exit
t.start()
# ---- channels ---- # ---- channels ----
......
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
from golang import go, chan, select, default, _PanicError from golang import go, chan, select, default, _PanicError
from pytest import raises from pytest import raises
import time, threading from os.path import dirname
import sys, time, threading, subprocess
# tdelay delays a bit. # tdelay delays a bit.
# #
...@@ -30,6 +31,13 @@ def tdelay(): ...@@ -30,6 +31,13 @@ def tdelay():
time.sleep(1E-3) # 1ms time.sleep(1E-3) # 1ms
def test_go():
# leaked goroutine behaviour check: done in separate process because we need
# to test process termination exit there.
subprocess.check_call([sys.executable,
dirname(__file__) + "/golang_test_goleaked.py"])
def test_chan(): def test_chan():
# sync: pre-close vs send/recv # sync: pre-close vs send/recv
ch = chan() ch = chan()
......
#!/usr/bin/env python
# Copyright (C) 2018 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""This program tests that leaked goroutines don't prevent program to exit"""
from __future__ import print_function
from golang import go, chan
import os, sys, time
def main():
ng = 100 # N(tasks) to spawn
gstarted = chan() # main <- g
mainexit = chan() # main -> all g
# a task that wants to live longer than main
def leaktask():
gstarted.send(1)
mainexit.recv()
# normally when main thread exits, the whole process is terminated.
# however if go spawns a thread with daemon=0, we are left here to continue.
# make sure it is not the case
time.sleep(3)
print("leaked goroutine: process did not terminate", file=sys.stderr)
sys.stderr.flush()
time.sleep(1)
os._exit(1) # not sys.exit - that can be used only from main thread
for i in range(ng):
go(leaktask)
# make sure all tasks are started
for i in range(ng):
gstarted.recv()
# now we can exit
mainexit.close()
sys.exit(0)
if __name__ == '__main__':
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment