4 changed files with 384 additions and 2 deletions
@ -0,0 +1,137 @@
|
||||
#!/usr/bin/perl |
||||
use strict; |
||||
use warnings; |
||||
use Time::HiRes qw(usleep); |
||||
use IPC::Open3; |
||||
use IO::Handle; |
||||
|
||||
# ANSI color codes |
||||
my $RESET = "\e[0m"; |
||||
my $GREEN = "\e[32m"; # Child color |
||||
my $YELLOW = "\e[33m"; # Parent color |
||||
|
||||
# Usage function |
||||
sub usage { |
||||
my ($args) = @_; |
||||
print STDERR "rcvd : $0 $args\n"; |
||||
print STDERR "usage:\n"; |
||||
print STDERR " $0 pump <persecond> <maxcount>\n"; |
||||
print STDERR " $0 parent\n"; |
||||
print STDERR " $0 child <delay_ms>\n"; |
||||
print STDERR "\n"; |
||||
print STDERR "e.g:\n"; |
||||
print STDERR " perl $0 pump 35 50 | perl $0 parent\n"; |
||||
exit 1; |
||||
} |
||||
|
||||
# Pump role |
||||
sub pump { |
||||
my ($persecond, $maxcount) = @_; |
||||
if ($persecond > 1000) { |
||||
print STDERR "WARNING: (>1000) sub millisecond scheduling not available - will go full speed\n"; |
||||
usleep(500_000); |
||||
} |
||||
|
||||
STDOUT->autoflush(1); |
||||
STDERR->autoflush(1); |
||||
|
||||
my $counter = -1; |
||||
my $ms = int(1000 / $persecond); |
||||
|
||||
while ($maxcount <= 0 || $counter < $maxcount - 1) { |
||||
$counter++; |
||||
print ".${counter}"; |
||||
usleep($ms * 1000); |
||||
} |
||||
|
||||
print STDERR "pump-done\n"; |
||||
} |
||||
|
||||
# Parent role |
||||
sub parent { |
||||
print STDERR "${YELLOW}parent${RESET}\n"; |
||||
usleep(250_000); |
||||
|
||||
# Read the first chunk from stdin |
||||
my $parent_chunk1; |
||||
read(STDIN, $parent_chunk1, 8); |
||||
print STDERR "${YELLOW}${parent_chunk1}${RESET}\n"; |
||||
|
||||
# Launch the child process |
||||
#my $child_pid = open3("<&STDIN", my $child_out, ">&STDERR", "perl", $0, "child", "150"); |
||||
open(local *CHILD_STDIN, "<&", \*STDIN) or die "Can't dup STDIN: $!"; |
||||
open(local *CHILD_STDERR, "<&", \*STDERR) or die "Can't dup STDERR: $!"; |
||||
my $child_pid = open3("<&CHILD_STDIN", my $child_out, ">&CHILD_STDERR", "perl", $0, "child", "150"); |
||||
|
||||
binmode($child_out, ":utf8"); |
||||
|
||||
# Handle output from the child process asynchronously |
||||
while (my $line = <$child_out>) { |
||||
print STDERR $line; |
||||
} |
||||
#close $child_out; |
||||
|
||||
waitpid($child_pid, 0); |
||||
|
||||
print STDERR "parent-tail-read\n"; |
||||
while (my $chunk = <STDIN>) { |
||||
print STDOUT $chunk; |
||||
} |
||||
|
||||
print STDERR "\n${YELLOW}parent-done${RESET}\n"; |
||||
} |
||||
|
||||
# Child role |
||||
sub child { |
||||
my ($delay_ms) = @_; |
||||
print STDERR "\n${GREEN}child${RESET}\n"; |
||||
usleep($delay_ms * 1000); |
||||
|
||||
# Read exactly 16 characters from stdin |
||||
my $chunk; |
||||
my $bytes_read = read(STDIN, $chunk, 16); |
||||
if (defined $bytes_read) { |
||||
if ($bytes_read > 0) { |
||||
print STDERR "${GREEN}child-read: $bytes_read bytes${RESET}\n"; |
||||
} else { |
||||
print STDERR "${GREEN}child-read: no data read${RESET}\n"; |
||||
exit 0; |
||||
} |
||||
} |
||||
print STDOUT "${GREEN}${chunk}${RESET}\n"; |
||||
|
||||
print STDERR "child-done\n"; |
||||
exit 0; |
||||
} |
||||
|
||||
# Main function |
||||
sub main { |
||||
my @args = @ARGV; |
||||
if (@args < 1) { |
||||
usage(""); |
||||
} |
||||
|
||||
my $role = shift @args; |
||||
if ($role eq "pump") { |
||||
if (@args != 2) { |
||||
usage("pump"); |
||||
} |
||||
my ($persecond, $maxcount) = @args; |
||||
pump($persecond, $maxcount); |
||||
} elsif ($role eq "parent") { |
||||
if (@args != 0) { |
||||
usage("parent"); |
||||
} |
||||
parent(); |
||||
} elsif ($role eq "child") { |
||||
if (@args != 1) { |
||||
usage("child"); |
||||
} |
||||
my ($delay_ms) = @args; |
||||
child($delay_ms); |
||||
} else { |
||||
usage($role); |
||||
} |
||||
} |
||||
|
||||
main(); |
@ -0,0 +1,125 @@
|
||||
import sys |
||||
import time |
||||
import asyncio |
||||
from subprocess import Popen, PIPE |
||||
|
||||
RESET = "\033[0m" |
||||
C = "\033[32m" # Child color green |
||||
P = "\033[33m" # Parent color yellow |
||||
|
||||
|
||||
def usage(): |
||||
print("Usage:") |
||||
print(" stdin_race.py pump <persecond> <maxcount>") |
||||
print(" stdin_race.py parent") |
||||
print(" stdin_race.py child <delay_ms>") |
||||
print("\nExample:") |
||||
print(" python stdin_race.py pump 35 50 | python stdin_race.py parent") |
||||
sys.exit(1) |
||||
|
||||
|
||||
async def pump(persecond, maxcount): |
||||
if persecond > 1000: |
||||
print("WARNING: (>1000) sub-millisecond scheduling not available - will go full speed", file=sys.stderr) |
||||
await asyncio.sleep(0.5) |
||||
|
||||
counter = -1 |
||||
ms = 1000 / persecond |
||||
|
||||
async def pump_emit(): |
||||
nonlocal counter |
||||
try: |
||||
counter += 1 |
||||
print(f".{counter}", end="", flush=True) |
||||
except BrokenPipeError: |
||||
return False |
||||
return True |
||||
|
||||
async def pump_schedule(): |
||||
nonlocal counter |
||||
while maxcount <= 0 or counter < maxcount - 1: |
||||
if not await pump_emit(): |
||||
break |
||||
await asyncio.sleep(ms / 1000) |
||||
print("pump-done", file=sys.stderr, flush=True) |
||||
|
||||
await pump_schedule() |
||||
|
||||
|
||||
async def parent(): |
||||
print(f"{P}parent{RESET}", file=sys.stderr, flush=True) |
||||
await asyncio.sleep(0.25) |
||||
|
||||
# Read the first chunk from stdin |
||||
parent_chunk1 = sys.stdin.read(8) |
||||
print(f"{P}{parent_chunk1}{RESET}", file=sys.stderr, flush=True) |
||||
|
||||
# Launch the child process |
||||
child_proc = Popen( |
||||
[sys.executable, __file__, "child", "150"], |
||||
stdin=sys.stdin, stdout=PIPE, stderr=sys.stderr, text=True |
||||
) |
||||
|
||||
# Forward stdin to the child process |
||||
#while True: |
||||
# chunk = sys.stdin.read(1) |
||||
# if not chunk: |
||||
# break |
||||
# try: |
||||
# child_proc.stdin.write(chunk) |
||||
# #child_proc.stdin.flush() |
||||
# except BrokenPipeError: |
||||
# print("Broken pipe error, child process may have exited.", file=sys.stderr) |
||||
# break |
||||
child_proc.wait() |
||||
|
||||
#child_proc.wait() |
||||
|
||||
print("parent-tail-read", file=sys.stderr, flush=True) |
||||
while True: |
||||
chunk = sys.stdin.read(1) |
||||
if not chunk: |
||||
break |
||||
print(chunk, end="", file=sys.stderr, flush=True) |
||||
|
||||
print(f"\n{P}parent-done{RESET}", flush=True) |
||||
|
||||
|
||||
async def child(delay_ms): |
||||
print(f"\n{C}child{RESET}", file=sys.stderr, flush=True) |
||||
await asyncio.sleep(delay_ms / 1000) |
||||
|
||||
# Read exactly 16 characters from stdin |
||||
chunk = sys.stdin.read(16) |
||||
print(f"{C}{chunk}{RESET}", file=sys.stderr, flush=True) |
||||
|
||||
print("child-done", file=sys.stderr, flush=True) |
||||
sys.exit(0) |
||||
|
||||
|
||||
def main(): |
||||
if len(sys.argv) < 2: |
||||
usage() |
||||
|
||||
role = sys.argv[1] |
||||
if role == "pump": |
||||
if len(sys.argv) != 4: |
||||
usage() |
||||
persecond = int(sys.argv[2]) |
||||
maxcount = int(sys.argv[3]) |
||||
asyncio.run(pump(persecond, maxcount)) |
||||
elif role == "parent": |
||||
if len(sys.argv) != 2: |
||||
usage() |
||||
asyncio.run(parent()) |
||||
elif role == "child": |
||||
if len(sys.argv) != 3: |
||||
usage() |
||||
delay_ms = int(sys.argv[2]) |
||||
asyncio.run(child(delay_ms)) |
||||
else: |
||||
usage() |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -0,0 +1,115 @@
|
||||
chan configure stdin -blocking 0 -buffering none |
||||
#puts stderr "stdinconf: [chan configure stdin]" |
||||
set RST \x1b\[0m |
||||
set C \x1b\[32m ;#child colour green |
||||
set P \x1b\[33m ;#parent colour yellow |
||||
|
||||
|
||||
proc usage {args} { |
||||
puts stderr "rcvd : [info script] $args" |
||||
puts stderr "usage:" |
||||
puts stderr " [info script] pump <persecond> <maxcount>" |
||||
puts stderr " [info script] parent" |
||||
puts stderr " [info script] child <delay_ms>" |
||||
puts stderr \n |
||||
puts stderr "e.g:" |
||||
puts stderr " >tclsh" |
||||
puts stderr " %chan configure stdin -blocking 0" |
||||
puts stderr " %tclsh [info script] pump 35 50 | tclsh [info script] parent" |
||||
exit 0 |
||||
} |
||||
|
||||
proc read_child {chan} { |
||||
if {![eof $chan]} { |
||||
puts stdout [read $chan] |
||||
flush stdout |
||||
} else { |
||||
set ::done 1 |
||||
} |
||||
} |
||||
|
||||
proc pump_schedule {} { |
||||
upvar ::counter c |
||||
upvar ::maxcount maxcount |
||||
if {$::forever_pump} { |
||||
if {$maxcount > 0 && $c >= $maxcount} { |
||||
set ::forever_pump 0 |
||||
} else { |
||||
after idle [list after 0 ::pump_emit] |
||||
} |
||||
tailcall after $::ms ::pump_schedule |
||||
} else { |
||||
after idle [list ::pump_end] |
||||
} |
||||
} |
||||
proc pump_emit {} { |
||||
upvar ::counter c |
||||
if {[catch { |
||||
puts -nonewline stdout .[incr c] |
||||
}]} { |
||||
set ::forever_pump 0 |
||||
} |
||||
flush stdout |
||||
} |
||||
proc pump_end {} { |
||||
puts stderr "pump-done" |
||||
flush stderr |
||||
flush stdout |
||||
} |
||||
|
||||
switch -- [lindex $::argv 0] { |
||||
pump { |
||||
if {$::argc != 3} {usage {*}$::argv} |
||||
set persec [lindex $::argv 1] |
||||
set maxcount [lindex $::argv 2] |
||||
if {$persec > 1000} { |
||||
puts stderr "WARNING: (>1000) sub millisecond scheduling not available - will go full speed" |
||||
flush stderr |
||||
after 500 |
||||
} |
||||
chan configure stdout -blocking 1 -buffering none |
||||
set counter -1 |
||||
set ms [expr {1000 / $persec}] |
||||
set ::forever_pump 1 |
||||
|
||||
pump_schedule |
||||
vwait ::forever_pump |
||||
} |
||||
parent { |
||||
if {$::argc != 1} {usage {*}$::argv} |
||||
puts stderr "${::P}parent$RST" |
||||
after 250 |
||||
set parent_chunk1 [read stdin 8] |
||||
#set rdout [open |[concat tclsh [info script] child 150 2>@stdout <@stdin] RDONLY] |
||||
set rdout [open |[concat tclsh [info script] child 150 2>@stdout <@stdin] RDONLY] |
||||
chan conf $rdout -blocking 0 -buffersize 1 |
||||
chan event $rdout readable [list ::read_child $rdout] |
||||
|
||||
puts -nonewline stderr $::P$parent_chunk1$::RST |
||||
flush stderr |
||||
|
||||
after 10000 {set ::done 1} |
||||
vwait ::done |
||||
puts stdout parent-tail-read |
||||
while {![eof stdin]} { |
||||
puts -nonewline stderr [read stdin] |
||||
flush stderr |
||||
} |
||||
puts stdout \n${::P}parent-done$::RST |
||||
flush stdout |
||||
} |
||||
child { |
||||
if {$::argc != 2} {usage $::argv} |
||||
set delay_ms [lindex $::argv 1] |
||||
puts stdout "\n${::C}child$::RST" |
||||
after $delay_ms |
||||
puts stdout ${::C}[read stdin 16]$::RST |
||||
#puts stderr ${::C}[read stdin]$::RST |
||||
puts stdout "child-done" |
||||
flush stderr |
||||
exit 0 |
||||
} |
||||
default {usage $::argv} |
||||
} |
||||
exit 0 |
||||
|
Loading…
Reference in new issue