Home of the Squeezebox™ & Transporter® network music players.
Results 1 to 7 of 7
  1. #1
    Babelfish's Best Boy mherger's Avatar
    Join Date
    Apr 2005
    Location
    Switzerland
    Posts
    20,315

    How to read output from a background task in anon-blocking way?...

    I've been struggling for a while to get this working: in Spotty I'm
    using backticks to run the helper to get tokens etc.:

    my $token = `spotty --get-token...`;

    That's a blocking call. Unfortunately spotty sometimes seems to block
    for minutes (and I haven't found a way to fix this in the first
    place...). So... my next attempt was to implement this in a non-blocking
    way. I tried LMS' Slim::Utils::Network::sysreadline() - which would
    indeed return after a given timeout - but the process would keep running
    in the background and still lock up LMS somehow.

    Or I tried to register a reader in Slim::Networking::Select. But this
    didn't change the locking behaviour at all.

    Quite obviously I'm doing something wrong. In particular
    Slim::Utils::Network::sysreadline() seems to promise exactly what I'm
    looking for. Yet I'm failing badly.

    And I looked at some of Perl's lower level code such as Fcntl, too.

    Would anybody out there found a solution to pipe an external task's
    output to LMS?

    --

    Michael

  2. #2
    Senior Member
    Join Date
    Oct 2005
    Location
    Ireland
    Posts
    17,862
    For CDplayer plugin I used Proc::Background - the command line redirected output to a file and a timer checked process status to see when process has finished.

    The cdda2wav program took seconds to execuet command on CDROM so it might be appicable to spotty if ti is takes seconds.

  3. #3
    Babelfish's Best Boy mherger's Avatar
    Join Date
    Apr 2005
    Location
    Switzerland
    Posts
    20,315

    How to read output from a background task inanon-blocking way?...

    > For CDplayer plugin I used Proc::Background - the command line
    > redirected output to a file and a timer checked process status to see
    > when process has finished.


    That was the next approach I planned to implement. I just don't like
    polling in general...

    > The cdda2wav program took seconds to execuet command on CDROM so it
    > might be appicable to spotty if ti is takes seconds.


    It usually doesn't. But I've seen times where it took literally minutes.
    Proc::Background would allow me to easily kill it after some timeout.

    --

    Michael

  4. #4
    Senior Member
    Join Date
    Oct 2005
    Location
    Ireland
    Posts
    17,862
    This is the sort of code to run and poll the task. Creating the command line was messy as it was different for windows vs Linux - you shouldn't have that problem.
    There are neater ways to copy output file to a variable.

    Code:
    sub go
    {
    	my $self = shift;
    	$log->debug("Fork executing '".$self->{command}."' with '".$self->{params}."'");
    	$log->debug("Fork actual executing '".$self->{syscommand}."'");
    	my $forkout    = $self->{forkout};
    	my $syscommand = $self->{syscommand};
        
    	unlink $forkout;
    
    	$self->{proc} = Proc::Background->new($syscommand) || $log->error("Child task forked: failed: $!");
    
    	$log->debug("Child task (". $self->{proc}->pid .") forked: " . $syscommand);
    	if ($self->{proc}->alive ){
    		$log->debug("Child task is alive ");
    	} else {
        		$log->debug("Child task has died/completed at startup");
      	}
    
      # Set up the callback
      	my $interval = $self->{pollingInterval};
      	Slim::Utils::Timers::setTimer($self,
      	                              Time::HiRes::time()+$self->{pollingInterval},
      	                              \&checkFork, ($self)
      	                              );
    }
    
    # Gets invoked by the Timer service every pollingInterval.
    # Check for any more output from the forked task
    sub checkFork()
    {
    	my $self = shift;
    	my $done=0; 
    
    	my $proc = $self->{proc};
    	my $logfile;
    
    	if ($proc->alive ) {
        # Use the callback Status test for time when cdda2wav prompts user for CD
    		my $callback=$self->{completionStatusTest};
    		if (defined($callback )) {
    			my $param=$self->{completionParam};
    			&$callback($self,$param);
    		}
    	}
    	else {
    		my $output;
    		my $pid = $proc->pid;
    		$log->debug("Forked task $pid is not alive");
        		open ($logfile, $self->{forkout} ) or  $log->error("Fork $pid dead: Can't open ". $self->{forkout});
        		while (my $line = <$logfile>) {
          			$log->debug("FORK $pid : $line");
          			$output .= $line;
    		}
    
    		$self->{output} = $output;         
    		close($logfile);      
    		$log->debug("Forked task complete, invoking callback");
    
    	 	my $callback=$self->{completionCallback};
    		my $param=$self->{completionParam};
    		&$callback($param, $output);
    
    		$done=1;
        
    		$log->debug("Deleting Bat and output files ". $self->{forkout});
    		unlink $self->{forkout};
    		if ($self->{batfile}) { unlink $self->{batfile}; } ;
    	};
    
    	if (!$done) {
    		my $interval = $self->{pollingInterval};
    		Slim::Utils::Timers::setTimer($self,
                    	                  Time::HiRes::time()+$self->{pollingInterval},
                            	          \&checkFork, ($self)
                              	        );
      	} ;
    }

  5. #5
    Babelfish's Best Boy mherger's Avatar
    Join Date
    Apr 2005
    Location
    Switzerland
    Posts
    20,315

    How to read output from a background task inanon-blocking way?...

    > This is the sort of code to run and poll the task. Creating the command
    > line was messy as it was different for windows vs Linux - you shouldn't
    > have that problem.


    Thanks! That's a good starting point indeed.

    > There are neater ways to copy output file to a variable.


    I usually do:

    use File::Slurp;
    my $output = read_file($logfile);

    --

    Michael

  6. #6
    Senior Member
    Join Date
    May 2008
    Location
    Canada
    Posts
    5,061
    If I understood well, I had a similar issue when I became maintainer of Shairtunes2 and move it to Windows. I ended up implementing a socket on localhost and added a select on LMS event loop for events on that socket. I would have to look back at what I did, but from memory the Perl part waits creates and waits on a port which is a parameter to the helper so that it can send data on that port at anytime and I can wait in a non-blocking way in Perl. Safety timers will close socket and kill the helper if nothing happens after an extended timeout.
    LMS 7.7, 7.8 and 7.9 - 5xRadio, 3xBoom, 4xDuet, 1xTouch, 1 SB2. Sonos PLAY:3, PLAY:5, Marantz NR1603, JBL OnBeat, XBoxOne, XBMC, Foobar2000, ShairPortW, JRiver 21, 2xChromecast Audio, Chromecast v1 and v2, , Pi B3, B2, Pi B+, 2xPi A+, Odroid-C1, Odroid-C2, Cubie2, Yamaha WX-010, AppleTV 4, Airport Express, GGMM E5

  7. #7
    Babelfish's Best Boy mherger's Avatar
    Join Date
    Apr 2005
    Location
    Switzerland
    Posts
    20,315

    How to read output from a background task inanon-blocking way?...

    > If I understood well, I had a similar issue when I became maintainer of
    > Shairtunes2 and move it to Windows. I ended up implementing a socket on
    > localhost and added a select on LMS event loop for events on that
    > socket.


    I considered having the helper call back to the plugin over http (as
    I've already implemented such a mechanism for the Connect mode). But I
    prefer not to touch the Rust code as any change or bugfix requires
    building those binaries... something you've optimized far better than I
    have :-)

    I'm currently running a version using Proc::Background and polling the
    existence of a file with the token information. Seems to be working fine
    so far. The kids haven't complained yet.
    --

    Michael

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •