Re: [mh] Process_item_queue <more partly solved>

Giles Godart-Brown <[email protected]> Wed, 24 Feb 2021 16:32:51 +0000
Newsgroups gmane.comp.misc.misterhouse.user
Message-ID <[email protected]>
I've made a lot of progress this week, this version still uses a single 
queue, but it sorts it by process_name and time and then runs ones with 
different names in parallel, so it appears like there is a queue for 
each process.

It currently has a lot of debug prints and tests in it as I try to check 
all the edge cases (no guarantees that I've succeeded) .  I will remove 
these if you think it worth creating a pull to incorporate it into 
MisterHouse Master. If so I'll need some help.

I've still failed to be able to build a 3D hash array to include process 
with more than one command, so it only works for processes with a single 
command and throws an error if you try to queue a process with more than 
one.

Giles

On 20/02/2021 15:08, Giles Godart-Brown wrote:
>
> I've now fixed the problem with changes to the process item before it 
> was removed from the queue.
>
> There are 2 limitations with this solution;
>
> 1) its a single FIFO, all process_items are executed in the order they 
> arrive on the queue
>
> 2) I struggled with array references and arrays of arrays, so there is 
> a limit to a single command process_item i.e. no add('program3 
> arguments', 'program4 arguments', ...). It simply runs the first 
> command assigned to the process_item.
>
> I think this can be improved by adding a time_added_to_queue column 
> and sorting the queue on process_name and time_added_to_queue.
>
> Then when it processes the queue instead of it just taking the top of 
> the queue it takes the first entry for each process_name i.e. a sort 
> of parallel queue.
>
> If I get some time over the weekend I'll see if I can implement this.
>
> I'm no expert on the innards of MisterHouse and how the modules work, 
> so If you think it worthwhile I'd like some help turning this into a 
> module for a future pull request.
>
> Thanks in anticipation
>
> Giles
>
>
> On 19/02/2021 19:17, Giles Godart-Brown wrote:
>> Attached is a first go at building a process_item_queue.
>>
>> Its a single queue, and I'm sure one queue per process_item would be 
>> better, but I've run out of talent :-)
>>
>> Giles
>>
>> On 18/02/2021 18:28, Giles Godart-Brown wrote:
>>> I was going to build it for myself, then if its good enough (?) and 
>>> fulfils backwards compatibility I will add a pull
>>>
>>> G
>>>
>>> On 18/02/2021 17:25, Jeff Siddall via misterhouse-users wrote:
>>>> Giles,
>>>>
>>>> When you say "I'll build a queue" do you mean for your project or 
>>>> adding it onto process_items?  As long as it was backwards 
>>>> compatible with the existing behavior of a process_item, it would 
>>>> be nice to add that onto the process_item so there is a standard 
>>>> way for anyone to push items onto a process_item queue instead of 
>>>> everyone implementing it their own way.
>>>>
>>>> Jeff
>>>>
>>>>
>>>> On 2021-02-18 11:47 a.m., Giles Godart-Brown wrote:
>>>>>
>>>>> Thanks H
>>>>>
>>>>> I'll build a queue and update the docs over the weekend
>>>>>
>>>>> Giles
>>>>>
>>>>> On 18/02/2021 14:24, H Plato wrote:
>>>>>> As far as I know that’s how it works, the process_item has a 
>>>>>> single thread. You can check if the process_item is still active 
>>>>>> by calling$p_longproc->done(), and then queuing 
>>>>>> the additional commands to an array. That’s how I’ve dealt with 
>>>>>> this in some of my modules
>>>>>>
>>>>>>> On Feb 18, 2021, at 1:22 AM, Giles Godart-Brown 
>>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>>>
>>>>>>> I use process_items to send messages and urls, occasionally 
>>>>>>> (usually when I write bad code) I need to send a lot of emails 
>>>>>>> in quick succession but MisterHouse kills any unfinished ones, 
>>>>>>> rather than running them in parallel which is what I expected. 
>>>>>>> To test this I built a minimum reproducible case below where it 
>>>>>>> launches a process that simply sleeps for 5 seconds twice in 
>>>>>>> succession. As you can see from the print log, he first one gets 
>>>>>>> killed.
>>>>>>>
>>>>>>> Is this normal?
>>>>>>>
>>>>>>> Giles
>>>>>>>
>>>>>>> <mytest.pl>
>>>>>>>
>>>>>>> print_log( "Longproc run 1", "INFORMATIONAL", "p_longproc" );
>>>>>>> start $p_longproc;
>>>>>>> print_log( "Longproc run 2", "INFORMATIONAL", "p_longproc" );
>>>>>>> start $p_longproc;
>>>>>>> </mytest.pl>
>>>>>>>
>>>>>>> <longproc.sh>
>>>>>>>
>>>>>>> #!/bin/sh
>>>>>>> # shell to sleep
>>>>>>> echo longproc start 5 second sleep pid= $$
>>>>>>> sleep 5
>>>>>>> echo "longproc end"
>>>>>>> exit 0
>>>>>>> </longproc.sh>
>>>>>>>
>>>>>>> <print.log>
>>>>>>>
>>>>>>> 18/02/2021 08:10:27 [p_longproc] INFO Longproc run 1
>>>>>>> 18/02/2021 08:10:27 [p_longproc] INFO Longproc run 2
>>>>>>> Warning, a previous 'start' on this process has not finished yet
>>>>>>> Killing unfinished process id 9380 
>>>>>>> cmd=//home/pi/mh/GGBcode/procs/longproc.sh
>>>>>>>
>>>>>>> ...
>>>>>>>
>>>>>>> 18/02/2021 08:10:33 [p_longproc] longproc start 5 second sleep 
>>>>>>> pid= 9381
>>>>>>> 18/02/2021 08:10:33 [p_longproc] longproc end
>>>>>>>
>>>>>>> </print.log>
>>>>>>>
>>>>>>> ________________________________________________________
>>>>>>> To unsubscribe from this list, go to: 
>>>>>>> https://lists.sourceforge.net/lists/listinfo/misterhouse-users
>>>>>>>
>>>>>>
>>>>>
>>>>> <http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> 
>>>>>     Virus-free. www.avg.com 
>>>>> <http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> 
>>>>>
>>>>>
>>>>> <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>
>>>>>
>>>>>
>>>>> ________________________________________________________
>>>>> To unsubscribe from this list, go to: 
>>>>> https://lists.sourceforge.net/lists/listinfo/misterhouse-users
>>>>>
>>>>
>>>>
>>>> ________________________________________________________
>>>> To unsubscribe from this list, go to: 
>>>> https://lists.sourceforge.net/lists/listinfo/misterhouse-users
>>>>
>>
>>

________________________________________________________
To unsubscribe from this list, go to: https://lists.sourceforge.net/lists/listinfo/misterhouse-users
process_item_queue.pl (text/x-perl-script, 9.4 KB)
=head1 B<Process_Item_queue>

=head2 SYNOPSIS

  
=head2 DESCRIPTION

use this object to add process items to a queue. 
This version has a single queue called @process_item_queue, the next item on the queue is forked when the previous one finishes

=head2 INHERITS

<>

=head2 METHODS

add_process_item_to_queue add a process item to the queue, passed the  Process_item 
check_process_item_queue This is called regularly to see if it has any items to process on the queue

=over

=cut

#-----------------------------------------------------------------------
# tests
#-----------------------------------------------------------------------
# noloop=start
$my_test1 = new Voice_Cmd 'test [1,2,3,4]';
my $v_check_process_queue = new Voice_Cmd 'process queue';

$p_longproc = new Process_Item;
set $p_longproc $config_parms{"code_dir"} . "/procs/longproc.sh";
$p_longproc->set_output( $config_parms{data_dir} . "/logs/longproc.txt" );
set_timeout $p_longproc 15;

$p_longproc1 = new Process_Item;
set $p_longproc1 $config_parms{"code_dir"} . "/procs/longproc1.sh";
$p_longproc1->set_output( $config_parms{data_dir} . "/logs/longproc1.txt" );
set_timeout $p_longproc1 15;

# noloop=stop

if ( $state = said $my_test1) {
	if ( $state == 1 ) {
		set $p_longproc $config_parms{"code_dir"}
		  . "/procs/longproc.sh 12 \"test with a quote\"";
		set_timeout $p_longproc 15;
		add_process_item_to_queue($p_longproc);
	}
	elsif ( $state == 2 ) {
		set $p_longproc1 $config_parms{"code_dir"} . "/procs/longproc1.sh 12";
		set_timeout $p_longproc1 15;
		add_process_item_to_queue($p_longproc1);

	}

	elsif ( $state == 3 ) {
		purge_process_item_queue();
	}
	elsif ( $state == 4 ) {

	}
}

if ( done_now $p_longproc) {
	print_log( "Longproc done", "INFORMATIONAL", "p_longproc" );
	if ( timed_out $p_longproc) {
		print_log( 'timed out', "ERROR", "p_longproc" );
	}
	print_process_log( "longproc.txt", "p_longproc" );
}

if ( done_now $p_longproc1) {
	print_log( "Longproc1 done", "INFORMATIONAL", "p_longproc1" );
	if ( timed_out $p_longproc1) {
		print_log( 'timed out', "ERROR", "p_longproc1" );
	}
	print_process_log( "longproc1.txt", "p_longproc1" );
}

#-----------------------------------------------------------------------
# every  seconds check the queue
#-----------------------------------------------------------------------

#if ($New_Second) {
if ( $state = said $v_check_process_queue) {
	check_process_item_queue();
}

#-----------------------------------------------------------------------
# globals
#-----------------------------------------------------------------------

#-----------------------------------------------------------------------
# Add a process item to the queue
#-----------------------------------------------------------------------

sub add_process_item_to_queue {
	my $this_process_object = $_[0];

	if ( $this_process_object->{cmd_index} > 1 ) {
		print_log(
			substr( $this_process_object->{object_name}, 1 )
			  . " has more than one command so cannot be queued",
			"WARNING", "add_process_item_to_queue"
		);
		return;
	}
	else {
		print_log(
			"Adding "
			  . substr( $this_process_object->{object_name}, 1 ) . " cmds"
			  . $this_process_object->{cmds}[0]
			  . " timeout"
			  . $this_process_object->{timeout}
			  . " to the queue",
			"INFORMATIONAL", "add_process_item_to_queue"
		);
	}

	my $no_of_queued_items = scalar scalar @process_item_queue;
	$process_item_queue[$no_of_queued_items]{process_name} =
	  substr( $this_process_object->{object_name}, 1 );
	$process_item_queue[$no_of_queued_items]{cmds} =
	  $this_process_object->{cmds}[0];
	$process_item_queue[$no_of_queued_items]{cmds} =~ s/\"/\\\"/g;
	$process_item_queue[$no_of_queued_items]{timeout} =
	  $this_process_object->{timeout};
	$process_item_queue[$no_of_queued_items]{queued_time} =
	  time();
	$process_item_queue[$no_of_queued_items]{status} = "waiting";
}

#-----------------------------------------------------------------------
# delete a process item from the queue,
# passed the index of the item in the queue starting at 0
#-----------------------------------------------------------------------

sub delete_process_item_from_queue {
	my $row_to_delete      = $_[0];
	my $no_of_queued_items = scalar scalar @process_item_queue;
	if ( $row_to_delete < 0 || $row_to_delete > $no_of_queued_items ) {
		print_log(
			$row_to_delete . "is out of range  0 to " . $no_of_queued_items,
			"WARNING", "delete_process_item_from_queue" );

	}
	print_log( "Deleting " . $row_to_delete . " from the queue",
		"INFORMATIONAL", "delete_process_item_from_queue" );

	splice @process_item_queue, $row_to_delete, 1;
	return;
}

#-----------------------------------------------------------------------
# check the process_item_queue
# if the previous one is done, delete it and action the next
#-----------------------------------------------------------------------
sub check_process_item_queue {

	my (
		$this_queued_process_name, $this_queued_process,
		$this_process_object,      $next_process_object
	);
	my $count_items_in_queue = scalar @process_item_queue;
	if ( $count_items_in_queue == 0 ) {

		# queue is empty
		return;
	}
	else {
		print_log(
			"there are  " . $count_items_in_queue . " items in the queue",
			"INFORMATIONAL", "check_process_item_queue" );
		my $last_queued_process_name = "";

		# sort the queue on process_name and the time it was put on the queue
		my @sortedq =
		  sort {
			$a->{process_name} cmp $b->{process_name}
			  || $a->{queued_time} <=> $b->{queued_time}
		  } @process_item_queue;
		@process_item_queue = @sortedq;

		# go through the sorted list
		# if its running, go to next item
		# if this is the first instance of a process; if its not started, then start it
		# if its finished delete it test the next one of this name
		for ( my $i = 0; $i < $count_items_in_queue; $i++ ) {
			$this_queued_process_name = $process_item_queue[$i]{process_name};
			print_log(
				"Checking "
				  . $this_queued_process_name
				  . " queued at "
				  . datestamp_to_hms( $process_item_queue[$i]{queued_time} ),
				"INFORMATIONAL", "check_process_item_queue"
			);
			if ( $this_queued_process_name ne $last_queued_process_name ) {
				$last_queued_process_name = $this_queued_process_name;

				# this is the first  ocurrence of this process in the queue
				# has it finished, if not go to the next item on the queue
				print_log(
					"first time check for "
					  . $this_queued_process_name
					  . " queued at "
					  . datestamp_to_hms(
						$process_item_queue[$i]{queued_time} ),
					"INFORMATIONAL", "check_process_item_queue"
				);
				$this_process_object =
				  get_object_by_name($this_queued_process_name);

				# has the process started
				if ( $process_item_queue[$i]{status} eq "waiting" )
				{    # this process hasnt started, so start it and move on
					print_log(
						$this_queued_process_name
						  . " has not started, starting it and moving on",
						"INFORMATIONAL", "check_process_item_queue"
					);
					start_process_object(
						$process_item_queue[$i]{process_name},
						$process_item_queue[$i]{cmds},
						$process_item_queue[$i]{timeout}
					);
					$process_item_queue[$i]{status} = "running";
					next;
				}

				# has it finished
				if ( !defined( $this_process_object->{done} )
					&& $process_item_queue[$i]{status} eq "running" )
				{
					print_log( $this_queued_process_name . " is still running",
						"INFORMATIONAL", "check_process_item_queue" );

					# this process is still running, move onto next one

					next;
				}
				else {
					# this process has finished, delete it
					$process_item_queue[$i]{status} = "finished";
					print_log(
						$this_queued_process_name . " finished, deleting it",
						"INFORMATIONAL", "check_process_item_queue" );
					if ( $count_items_in_queue == 1 ) {
						print_log(
							$this_queued_process_name
							  . " finished, purging queue",
							"INFORMATIONAL", "check_process_item_queue"
						);
						purge_process_item_queue();
					}
					else {
						print_log(
							$this_queued_process_name
							  . " finished, deleting it",
							"INFORMATIONAL", "check_process_item_queue"
						);
						delete_process_item_from_queue($i);

						$i--;
						$count_items_in_queue--;
					}
					next;
				}

			}
		}
		return;
	}

}

#-----------------------------------------------------------------------
# start a process item given its name, cmds and timeout
#-----------------------------------------------------------------------
sub start_process_object {
	my ( $this_process_name, $this_process_cmds, $this_process_timeout ) = @_;
	my $command;
	$command =
	  "set \$" . $this_process_name . " \"" . $this_process_cmds . "\";";
	print_log( "running " . $command, "INFORMATIONAL", "start_process_name" );
	eval $command;
	$command =
	  "set_timeout \$" . $this_process_name . " " . $this_process_timeout . ";";
	print_log( "running " . $command, "INFORMATIONAL", "start_process_object" );
	eval $command;
	$command = "start \$" . $this_process_name . ";";
	print_log( "running " . $command, "INFORMATIONAL", "start_process_object" );
	eval $command;
	return;
}

#-----------------------------------------------------------------------
# purge process item queue
#-----------------------------------------------------------------------
sub purge_process_item_queue {
	print_log( "Purging process item queue ",
		"INFORMATIONAL", "purge_process_item_queue" );
	@process_item_queue = ();
	return;
}
########################################################################
# History:
# 19-02-2021 new code
# 20-02-2021 fixed for changes on each queue entry
longproc.sh (application/x-sh, 249 B)
#!/bin/sh
# shell to sleep
now=$(date +"%T")
if [ -z ${1+x} ]; 
  then sleeptime=1; 
  else sleeptime=$1; 
fi
echo "$now longproc start $sleeptime second sleep string = $2  pid= $$"
sleep $sleeptime
now=$(date +"%T")
echo "$now longproc end"
exit 0