PHP - Interrupting or suspending pthread's execution

php, pthreads

Solution

RandomSeeds answer is close, but open to race conditions.

<?php
class FileReader extends Thread {
    public $file;
    public $pause;

    public function __construct($file) {
        $this->file = $file;
        $this->pause = false;
    }

    public function run() {
        if (($handle = fopen($this->file, "rb"))) {
            $len = 0;
            do {
                $this->synchronized(function(){
                    if ($this->paused) {
                        printf(
                            "\npausing %lu ...\n", $this->getThreadId());
                        $this->wait();
                    }
                });

                $data = fread($handle, 1024);
                $len += strlen($data);

                if (($len % 2) == 0) {
                    printf( 
                        "\r\rread %lu", $len);
                }
            } while (!feof($handle));

            fclose($handle);
        }
    }

    public function pause() {
        return $this->synchronized(function(){
            return ($this->paused = true);
        });
    }

    public function unpause() {
        return $this->synchronized(function(){
            $this->paused = false;      
            if ($this->isWaiting()) {
                return $this->notify();
            }
        });
    }
}

function do_something($time) {
    $start = time();

    while (($now = time()) < ($start + $time)) {
        usleep(100);
        if (($now % 2) == 0) {
            echo ".";
        }
    }

    echo "\n";
}

$reader = new FileReader("/path/to/big/file.ext");
$reader->start();

sleep(2);
$reader->pause();
do_something(rand(2, 4));
$reader->unpause();
sleep(2);
$reader->pause();
do_something(rand(2, 4));
$reader->unpause();
sleep(2);
$reader->pause();
do_something(rand(2, 4));
$reader->unpause();
?>

It is important that variables used for purposes of synchronization are only ever access in synchronized blocks, I have omitted the implementation of a stop/quit function but it's logic is much the same, as RandomSeeds example shows.

Race conditions lurk within:

public function mine($data) {
    /* anyone can set doSynchronization at any time */
    if ($this->doSynchronization) {

        $this->synchronize(function(){
            /* checking the predicate in here is safer */
            $this->wait();
        });
    }
}

Good:

public function mine($data) {
    $this->synchronize(function(){
        if ($this->doSynchronization) {
            $this->wait();
        }
    });
}

Extreme:

public function mine($data) {
    $this->synchronize(function(){
        while ($this->doSynchronization) {
            $this->wait();
        }
    });
}

The posix standard would always have you write it the extreme way, I'm not so fussed, whatever works for you. The reason for this extreme code is, allowances must be made for a thread to receive a signal other than the one it is waiting on, many low level signals may result in a thread waking from a call to pthread_cond_wait; checking the predicate in a loop like that guards against what the specification calls spurious wakeups ... but such extreme measures can also lead to ill side effects; the reason those threads receive the low level signal is because it is necessary for them to take some action of some kind, ignoring that can easily cause a different part of the stack to deadlock because it expected your thread to die (or do something else, die is an example) when it was signalled ...

Problem

How to interrupt an execution of a thread from the main context? In the snippet below - how would one go about stopping the action that the thread does without destroying it? ``` class ReadFileThread extends Thread { public function __construct($file, $chunk = 1024) { $this->file = $file; $this->chunk = $chunk; } public function run() { if(is_file($this->file) && is_readable($this->file)) { $fh = fopen($this->file, 'rb'); while(!feof($fh)) { $content = fread($fh, $this->chunk); } fclose($fh); } } } $num = 10; $threads = []; for($i = 0; $i < $num; $i++) { $thread = new ReadFileThread('/path/to/10gig_file.txt', 1024); $threads[] = $thread; // I start the thread, now it's detached from the main context and is reading the file asynchronously $thread->start(); } // The interesting part - I want to random 1 thread whose operation of file reading I want to interrupt $to_interrupt = $threads[rand(0, $num)]; // How to interrupt the thread without destroying it? I want its context preserved ```

Original source