Question: class attribute doesn't get assigned correctly

Question

class attribute doesn't get assigned correctly

Answers 2
Added at 2016-12-31 18:12
Tags
Question

I am trying to loop an alarm (in the file beep.wav) and show an alert.

Once the alert is closed, I want to instantly stop the alarm.

I am attempting a solution which uses threading to control the alarm's playback.

However, it throws an error:

Traceback (most recent call last):
  File "test.py", line 28, in <module>
    thread.stop()
  File "test.py", line 21, in stop
    self.process.kill()
AttributeError: 'AlarmThread' object has no attribute 'process'

I don't really know why this error would get thrown, but it looks like self.process is, for some reason, not assigned when AlarmThread.stop is called.

This makes no sense to me, as from my code it looks like thread.stop is only called after thread.start:

import subprocess
import threading

class AlarmThread(threading.Thread):
    def __init__(self, file_name="beep.wav"):
        super(AlarmThread, self).__init__()
        self.file_name = file_name
        self.ongoing = None

    def run(self):
        self.ongoing = True
        while self.ongoing:
            self.process = subprocess.Popen(["afplay", self.file_name])
            self.process.wait()

    def stop(self):
        if self.ongoing is not None:
            self.ongoing = False
            self.process.kill()

thread = AlarmThread()
thread.start()
# show_alert is synchronous, an alert must be closed before the script continues
show_alert("1 second timer")

thread.stop()
thread.join()
Answers
nr: #1 dodano: 2016-12-31 18:12

You have a race condition. The thread hasn't had time to start, create the process and assign self.process by the time you call thread.stop(). You could initialize self.process in __init__ and use that to see if the process is really there

import subprocess
import threading

class AlarmThread(threading.Thread):
    def __init__(self, file_name="beep.wav"):
        super(AlarmThread, self).__init__()
        self.lock = threading.Lock()
        self.file_name = file_name
        self.ongoing = False
        self.process = None

    def run(self):
        self.ongoing = True
        while True:
            with self.lock:
                if not self.ongoing:
                    break
                self.process = subprocess.Popen(["afplayer", self.file_name])
            self.process.wait()

    def stop(self):
        with self.lock:
            if self.ongoing:
                self.ongoing = False
                if self.process:
                    self.process.kill()


thread = AlarmThread()
thread.start()
# show_alert is synchronous, an alert must be closed before the script continues
show_alert("1 second timer")

thread.stop()
thread.join()
nr: #2 dodano: 2016-12-31 18:12

Yes, it was caused by a race condition.

The thread hasn't had time to start, create the process and assign self.process by the time you call thread.stop()

However, I found a fix that relied upon simply waiting until thread.process was assigned:

thread = AlarmThread()
thread.start()
while not thread.process:
    time.sleep(0.1)

show_alert(message)

thread.stop()
thread.join()

My class also changed slightly to ensure thread.process is always assigned:

class AlarmThread(threading.Thread):
    def __init__(self, file_name="beep.wav"):
        super(AlarmThread, self).__init__()
        self.file_name = file_name
        self.ongoing = None
        self.process = None

    def run(self):
        self.ongoing = True
        while self.ongoing:
            self.process = subprocess.Popen(["afplay", self.file_name])
            self.process.wait()
            self.process = None

    def stop(self):
        if self.ongoing is not None:
            self.ongoing = False
            self.process.kill()
Source Show
◀ Wstecz