7

I have written a multithreaded application to watch and respond to changes in given list of files. I have a Watch class which gets the file size and sets it to size variable upon first call. Then, after a few seconds it again gets the size of the file and compares it with with the previous size and, if changed, sets size to the current size of the file. Furthermore there is a WatchWorker class which is a subclass of threading.Thread. The WatchWorker which makes use of Watch class to 'watch ' a given file.

Now here is the real problem : The code I have written is working and notifies the user when a change is detected. But there is no response when I try to quit from the application using Ctrl+C. I'm on Windows.

Code:

import time
import threading
import os

class Watch(object):
    def __init__(self, path, time=5):
        self.path = path
        self.time = time
        self.size = os.stat(path).st_size



    def loop(self):
        while True:
            time.sleep(self.time)
            size = os.stat(self.path).st_size
            if size != self.size:
                self.size = size
                print "Change detected in file {}".format(self.path)



class Watch_Worker(threading.Thread):
    def __init__(self, path, *args, **kwargs):
        super(Watch_Worker, self).__init__(*args, **kwargs)
        self.path = path


    def run(self):
        super(Watch_Worker, self).run()
        Watch(self.path).loop()

def main(*args):
    for i in args:
        thrd = Watch_Worker(path=i)
        thrd.start()
        print 'Watching ' + i
        print "From main thread"



if __name__ == '__main__':
    main('blah.js', 'cs.c', 'ab.rb')

Edit

Modified code to compare the values produced os.stat('somefile.ext').st_size).

OMG coder
  • 167
  • 1
  • 7
  • 2
    This is not uncommon you should Google for this. If you want an answer you need to provide a minimum working example (code). – PyNEwbie Jan 01 '16 at 16:18
  • 1
    Reading all the files periodically is VERY resource consuming. I think you should check the modification date first, or use some kind of os specific file system watcher – Tamas Hegedus Jan 01 '16 at 16:20
  • @hege_hegedus I'm actually trying to make something like grunt-watch, and I wanted a cross platform solution too. However I will update my code to check modification time of the file soon. Thanks :) – OMG coder Jan 01 '16 at 16:26
  • btw do you mean Ctrl+C – PyNEwbie Jan 01 '16 at 16:30
  • @PyNEwbie But that's not related to this question in anyway! – OMG coder Jan 01 '16 at 16:51
  • Sorry this is the one I meant to grab http://stackoverflow.com/questions/11815947/cannot-kill-python-script-with-ctrl-c – PyNEwbie Jan 01 '16 at 17:12
  • Please include the code inside of the question itself, not as an external link (which is vulnerable to link rot). There are much better ways than reading a file every N seconds for checking for changes. – Colonel Thirty Two Jan 01 '16 at 17:12
  • 1
    You let the main thread exit, so CPython's `Py_Finalize` function calls `threading._MainThread._exitfunc`, which does a blocking `join` on each thread. The main thread can't respond to the `KeyboardInterrupt` exception while blocked. You should add a loop in `main` to `join` each thread with a timeout. Use a try /except to handle `KeyboardInterrupt` to set a value that tells all of your `Watch_Worker` threads to exit. – Eryk Sun Jan 01 '16 at 20:19
  • 2
    As a quick hack you can restore the default `SIGINT` handler that exits the process: `signal.signal(signal.SIGINT, signal.SIG_DFL)`. – Eryk Sun Jan 01 '16 at 20:22
  • 2
    Note that Windows doesn't actually have signals. The C runtime sets a console control handler that in turn calls the `SIGINT` and `SIGBREAK` handlers for console Ctrl+C and Ctrl+Break events. If you run this as a non-console program via pythonw.exe, there's no way to signal the process with `SIGINT`. So you really do need to manage your thread shutdown properly without relying on Ctrl+C. – Eryk Sun Jan 01 '16 at 20:31
  • The code shown does not work at all. Since `Watch.loop()` is never called by anything, there will be no output. There are other errors, this is the most glaring. Make sure what you post actually is what you are running. – msw Jan 02 '16 at 04:47
  • @msw What are the errors yoj came across? – OMG coder Jan 02 '16 at 06:08
  • OMG are you serious? I can't speak for windows, but the code shown exits immediately after the last `thrd.start()` because you never join the running threads. However I think the first defect I mentioned (never calling any code that does any work) is more than enough to call this a broken program. So again, what code are you actually running? – msw Jan 02 '16 at 09:26
  • @msw, these are not `daemon` threads. The interpreter doesn't let the main thread finalize and exit until each thread is joined. That's not specific to Windows, nor is Ctrl+C not working specific to Windows. The main thread is blocked while waiting on a thread, so the interpreter never gets to evaluate the `KeyboardInterrupt` exception set by Python's default `SIGINT` handler. If the system's default handler is restored, Ctrl+C will kill the process. – Eryk Sun Jan 02 '16 at 13:18
  • I have fixed it and is well and working. – OMG coder Jan 02 '16 at 13:59
  • Thank you all for your feedback. I'm new to Stack Overflow. Again, Thanks you all for your feedback. – OMG coder Jan 02 '16 at 14:20
  • @eryksun the OP's own answer below and the interpreter disagree with you. Did you, say, run, the code in question, or did you just decide to invent the content of your comment? Code is kinda annoyingly deterministic in that way. – msw Jan 02 '16 at 14:35
  • 1
    @msw, of course I ran the code from the question on both Linux and Windows. The OP is not setting `daemon=True` in the above code. He or she appears to have discovered that after I told you that "these are not `daemon` threads". And the solution of making them daemon threads is still not much better than disabling Python's `SIGINT` handler. It's a dirty solution. The OP should set a value for the threads to exit normally and let the interpreter `join` them. – Eryk Sun Jan 02 '16 at 14:44

1 Answers1

1

I solved the problem of the main thread being prevented from exiting by setting self.daemon = True. The main thread does not wait for the threads to exit. This is solved by adding a infinite while loop withtime.sleep(5) to the end of main function. Here is the code:

class Watch_Worker(threading.Thread):
        def __init__(self, path, time=2,  *args, **kwargs):
            super(Watch_Worker, self).__init__(*args, **kwargs)
            self.path = path
            self.time = time
            self.size = os.stat(path).st_size
            self.daemon = True


        def run(self):
            super(Watch_Worker, self).run()
            while True:
                time.sleep(self.time)
                size = os.stat(self.path).st_size
                if size != self.size:
                    self.size = size
                    print "Change detected in file {}".format(self.path)

    def main(*args):
        for i in args:
            thrd = Watch_Worker(path=i)
            thrd.start()
            print 'Watching ' + i
         while True:
             time.sleep(5)


    if __name__ == '__main__':
        main('blah.js', 'cs.c', 'ab.rb')
OMG coder
  • 167
  • 1
  • 7