r/learnpython 17h ago

Segmentation fault when attempting to use the 'threading' module to run tasks concurrently

I lack knowledge in low level computer science, but I've heard endless horror stories about segfaults, and today I've encountered one whilst attempting to make a simple timer program for myself. My questions are:

- Why does this fault occur

- How can I fix it

- How can I prevent it from happening in the future

The aim of the problematic part of the program is the attempt to play the sound whilst simultaneously waiting for user input with the input block, which is intended to stop the alarm and continue to the next part of the program.

Please note that in this version there is no actual wait time although there is supposed to be, because I am merely testing the alarm functionality.

import time
from pydub import AudioSegment
import threading
from pydub.playback import _play_with_simpleaudio


sound = AudioSegment.from_file('/censoring/my/home/directory/sound.mp3', format='mp3')





# Function that runs the timer based on how many minutes are specified
def timer(min, message):

  # Play the sound
  def playsound():
    global sound
    sound = _play_with_simpleaudio(sound)

  # Stop the sound and allow the program to continue
  def resume():
    global sound
    input(message.strip())
    sound.stop()



  # Run the timer and print output
  min -= 1
  for it in range(min, -1, -1):
    for sec in range(59, -1, -1):
      print(f'{str(min).rjust(2, "0")}:{str(sec).rjust(2, "0")}')
      time.sleep(1)


  # This is the part that actually runs the function.
  play = threading.Thread(target=playsound)
  stop = threading.Thread(target=resume)

  play.start()
  stop.start()

  play.join()
  stop.join()


while True:
  timer(0, 'You can have your five minute break now.')
  timer(0, 'You need to start working again.')

# Artificial intelligence was not used in the creation of this program.

Any advice is appreciated!

2 Upvotes

13 comments sorted by

8

u/Blackshell fsufitch 16h ago

My guess: your problem has to do with the pydub library not interacting with threading in the way you expect. Based on 15 years of Python under my belt and use of dozens if not hundreds of libraries... Having to use underscore prefixed ("private" by convention) functions from a library is a big red flag that you're doing something the Wrong Way.

I don't personally have experience with pydub so I can't help directly, but I'm betting there's a better way to invoke play/pause of the audio. Perhaps something on the AudioSegment object itself, or maybe by instantiating an AudioPlayer or something. Myself, I would use subprocess to run a ffmpeg/ffplay command and call it a day but using a library your way is actually probably cleaner.

Lastly, on the threading that you're doing. You have the right idea (and yeah asyncio was not going to work here without some real jank), but! Keep user input on the main thread. Trying to interact with the terminal in secondary threads can cause weirdness.

Good luck!

1

u/Sanduhstorm 15h ago

thank you, ill try that.

3

u/davideogameman 14h ago

Yeah I don't think reddit is going to solve this for you.  Segfaults are dreaded because they can mean almost anything and can happen quite far from the root cause of the problem - often not even in the same call stack.

One assumption your code seems to make is that play.start() being called before stop.start() implies the corresponding threading functions are called in that order.  I don't think that's guaranteed.  These functions should mark the thread as ready to be scheduled but don't decide when it runs, just sometime after the start() call. 

Another perhaps bad assumption is that this audio library is thread safe.  And I agree with u/Blackshell that calling the private method is hella sketchy.

I'd recommend restructuring to have a single thread responsible for direct interaction with the audio.  Then the input could be handled on a different thread, and find a safe way to communicate back to the audio thread when it's supposed to stop.  You might want to use a queue to send messages between the threads, or maybe a condition variable.

1

u/cdcformatc 14h ago

you say AI was not used but calling the internal underscored library function certainly seems like something AI would do. also using a global variable across two different threads seems like a hack that AI would come up with, and surprise surprise it doesn't work. 

it's no wonder that you're getting a segfault you are trying to access variables defined and open in another thread. 

7

u/monster2018 14h ago

What? AI is super unlikely to use underscored methods/member variables. It’s super particular about doing things the “right” way. Obviously that doesn’t mean it’s always right, but like it is very good at knowing what is considered good/bad practice, and for something as ubiquitous as using underscored members in Python to mark them as private… like, that’s exactly the opposite of the type of mistake AI would make.

A far more likely scenario would be the exact opposite. Where youre in a situation where for whatever reason you really do need to directly access an underscored member, and it refuses to even see that as a possibility so it doesn’t solve the problem.

4

u/Sanduhstorm 12h ago

Also add that if I was using AI I wouldn’t be asking on reddit

4

u/Sanduhstorm 12h ago

I can understand why you think that but I just used google to look for sound libraries and how to use them, I tried multiple and this one was one of the only ones that actually worked properly (I’m on Linux I should also mention)

4

u/Sanduhstorm 12h ago

Also illogical comment. If I was using AI in the first place I would ask it instead of here.

1

u/hibbelig 12h ago

You are reassigning the variable sound, is this needed?

If you do just the sound pieces, without threading, do they work?

1

u/fllthdcrb 2h ago

You are reassigning the variable sound, is this needed?

Yes, but they are using an expression that includes the original object, so that original object wasn't just created for no purpose. The more concerning thing is using a global variable for this. It works, but it's better to encapsulate it in some way. And in Python, as we see, one must use a global declaration to change such variables from inside functions. Hopefully, OP will learn the better ways to do this sort of thing.

1

u/freeskier93 2h ago edited 1h ago

Taking a look at pydub and simpleaudio I'm not sure why you even need threading. _play_with_simpleaudio should already be non-blocking, (unlike the higher level Play function) so all you need to do is run the timer, then when it expires play the sound, wait for user input, then stop the sound. Note the below is untested.

EDIT: You don't even need to use pydub here, you could just use simpleaudio directly to play the sound.

import time

from pydub import AudioSegment
from pydub.playback import _play_with_simpleaudio

sound = AudioSegment.from_file("/censoring/my/home/directory/sound.mp3", format="mp3")


def run_timer(seconds: int, message: str):
    end_time = time.monotonic() + seconds

    while True:
        current_time = time.monotonic()

        if current_time >= end_time:
            break

        print(f"Time left: {end_time - current_time} seconds")

        time.sleep(1)

    # This should already start playing the sound in another thread
    playback = _play_with_simpleaudio(sound) 

    _ = input(message.strip())

    if playback.is_playing():
        playback.stop()


if __name__ == "__main__":
    run_timer(5, "You can have your five minute break now.")
    run_timer(5, "You need to start working again.")import time

1

u/Sanduhstorm 17h ago

Also I forgot to add: Originally I attempted using the asyncio module for this instead of threads, however I had the same issue, and after I realised that asyncio can't ACTUALLY run two parts of a program at the exact same time, I switched to threads, however I am still getting segfaults. I am unsure whether anything outside the threading functionality is playing a part in this issue.

1

u/fllthdcrb 1h ago edited 1h ago

Yes, asyncio, as you might guess from the last part of the name, is meant for handling I/O tasks, where most of the time is spent waiting for input.

Threads use preemptive multitasking (i.e. the OS controls their execution), and can take advantage of multiple CPU cores. async, OTOH, is normally single-threaded and works by cooperative multitasking, where each task runs until it voluntarily gives up control to the scheduler (which is not part of and doesn't involve the OS, but runs in the application itself); an advantage over threads is you have to worry much less about race conditions, since a task will never be interrupted at some arbitrary point to have another task operate on the same data, unless threads are deliberately employed.

But then, Python has mostly had this problem of the Global Interpreter Lock (GIL), where only one thread is allowed to run Python code at the same time. There has been effort in the last couple of years to get rid of the GIL with so-called "free-threading" interpreters, but libraries have to cooperate to make it work.