I am trying to watch a directory using F_NOTIFY in Linux (Ubuntu 16, Python 3.5.2). However, it only works if I call fcntl from the main thread. Why are there no signals when I call fcntl from other threads? (I don't see any mention of threads in the python fcntl documentation. Also, if fcntl is failing, why doesn't it throw an exception?)
# Before running, create directory "a". To test, create a file in "a".
import fcntl, signal, os, threading
pipe_r, pipe_w = os.pipe()
def Unsafe_Handler(s,f):
os.write(pipe_w, b' ')
signal.signal(signal.SIGIO, Unsafe_Handler)
signal.siginterrupt(signal.SIGIO, False)
def Handler():
while True:
os.read(pipe_r, 1)
print("SIGNAL")
threading.Thread(target = Handler).start()
NOTIF_FLAGS = fcntl.DN_MODIFY | fcntl.DN_CREATE | fcntl.DN_MULTISHOT
def Watch_Dir(dn):
fd = os.open(dn, os.O_RDONLY)
fcntl.fcntl(fd, fcntl.F_SETSIG, 0)
fcntl.fcntl(fd, fcntl.F_NOTIFY, NOTIF_FLAGS)
print('Watching directory "%s", fd=%d' % (dn, fd))
return fd
def Init():
fd = Watch_Dir('a')
# this works
#Init()
# this doesn't work
t = threading.Thread(target = Init)
t.start()
t.join()
print("Awaiting signals...")
while True:
signal.pause()
Why signal handling in Python requires threads
The Handler thread is required because of the following combination of facts:
- The handler attached with
signal.signalcan't safely do anything but write to a pipe. - The main thread can't do real work either, for two reasons. First, because most blocking calls in the main thread will prevent the signal from being handled. Second, since the only allowed call is
signal.pause(), there is no way to safely wake up the main thread without a race condition (signal arrives just before thesignal.pause()) that could get the main thread stuck.
Why calling fcntl from the non-main thread is useful
This is simply because the main thread can't do real work, as already noted. (In my actual application, the handler needs to call fcntl. For this question I've created a simpler example though).