Posted to tcl by JMN at Thu Jul 31 03:29:23 GMT 2025view pretty

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=PIPE, stdout=PIPE, stderr=sys.stderr, text=True
    )

    # Forward stdin to the child process
    while True:
        chunk = sys.stdin.read(1)
        if not chunk:
            break
        print(f"{chunk}", file=sys.stdout, end='', flush=True)

    # Wait for the child process to finish
    child_proc.stdin.close()
    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 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()