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