ฉันจะสกัดกั้นเอาต์พุตที่ไม่มีบัฟเฟอร์ของ Proc :: Async ใน Raku ได้อย่างไร

Aug 19 2020

ด้วยตัวอย่างเช่น

# Contents of ./run
my $p = Proc::Async.new: @*ARGS; react { whenever Promise.in: 5 { $p.kill               }
    whenever $p.stdout { say "OUT: { .chomp }" } whenever $p.ready      { say "PID: $_" } whenever $p.start      { say "Done"            }
}

ดำเนินการเช่น

./run raku -e 'react whenever Supply.interval: 1 { .say }'

ฉันคาดว่าจะได้เห็นบางสิ่งบางอย่างเช่น

PID: 1234
OUT: 0
OUT: 1
OUT: 2
OUT: 3
OUT: 4
Done

แต่ฉันเห็นแทน

PID: 1234
OUT: 0
Done

ฉันเข้าใจว่าสิ่งนี้เกี่ยวข้องกับการบัฟเฟอร์: ถ้าฉันเปลี่ยนคำสั่งนั้นเป็นไฟล์

# The $|++ disables buffering ./run perl -E '$|++; while(1) { state $i; say $i++; sleep 1 }'

ฉันได้ผลลัพธ์ที่ต้องการ

ฉันรู้ว่าอ็อบเจ็กต์ TTY IO :: Handle ไม่ถูกบัฟเฟอร์และในกรณีนี้$*OUTกระบวนการที่เกิดไม่ใช่กระบวนการเดียว และฉันได้อ่านพบว่าIO :: ไพพ์อ็อบเจกต์ถูกบัฟเฟอร์ "ดังนั้นการเขียนโดยไม่มีการอ่านจะไม่ถูกบล็อกทันที" (แม้ว่าฉันจะไม่สามารถพูดได้ว่าฉันเข้าใจทั้งหมดว่าหมายถึงอะไรก็ตาม)

แต่ไม่ว่าฉันจะพยายามอย่างไรฉันก็ไม่สามารถรับสตรีมเอาต์พุตที่ไม่มีบัฟเฟอร์ของ Proc :: Async ได้ ฉันต้องทำอย่างไร

ฉันได้ลองผูก IO :: Handle แบบเปิดโดยใช้$proc.bind-stdoutแต่ฉันยังคงได้รับปัญหาเดิม

โปรดทราบว่าการทำบางสิ่งบางอย่างได้$proc.bind-stdout: $*OUTผลในแง่ที่วัตถุ Proc :: Async ไม่บัฟเฟอร์อีกต่อไป แต่ก็ไม่ใช่วิธีแก้ปัญหาของฉันเพราะฉันไม่สามารถแตะที่ผลลัพธ์ก่อนที่มันจะออกไป ฉันแนะนำว่าถ้าฉันสามารถผูก Proc :: Async กับแฮนเดิลที่ไม่มีบัฟเฟอร์ได้ก็ควรทำในสิ่งที่ถูกต้อง แต่ฉันยังไม่สามารถทำงานนั้นได้


เพื่อความชัดเจน: ตามที่แนะนำในตัวอย่าง Perl ฉันรู้ว่าฉันสามารถแก้ไขได้โดยปิดการใช้งานบัฟเฟอร์ของคำสั่งที่ฉันจะส่งเป็นอินพุต แต่ฉันกำลังมองหาวิธีที่จะทำสิ่งนี้จากด้านที่สร้าง Proc: : วัตถุ Async

คำตอบ

3 JonathanWorthington Aug 21 2020 at 05:13

Proc::Asyncตัวมันเองไม่ได้ทำการบัฟเฟอร์กับข้อมูลที่ได้รับ อย่างไรก็ตามกระบวนการที่เกิดขึ้นอาจทำเองได้ขึ้นอยู่กับสิ่งที่ส่งออกไปและนั่นคือสิ่งที่สังเกตได้ที่นี่

หลายโปรแกรมทำการตัดสินใจเกี่ยวกับการบัฟเฟอร์เอาต์พุต (นอกเหนือจากสิ่งอื่น ๆ เช่นว่าจะปล่อยรหัสสีหรือไม่) โดยขึ้นอยู่กับว่าที่จับเอาต์พุตแนบกับ TTY หรือไม่ สมมติฐานคือ TTY หมายถึงมนุษย์กำลังจะดูผลลัพธ์ดังนั้นเวลาแฝงจึงเป็นที่ต้องการของปริมาณงานดังนั้นการบัฟเฟอร์จึงถูกปิดใช้งาน (หรือ จำกัด เฉพาะการบัฟเฟอร์บรรทัด) ในทางกลับกันหากเอาต์พุตกำลังไปที่ไพพ์หรือไฟล์ข้อสันนิษฐานก็คือเวลาแฝงนั้นไม่สำคัญนักและการบัฟเฟอร์จะถูกใช้เพื่อให้ได้อัตราการรับส่งข้อมูลที่สำคัญ (ระบบเรียกให้เขียนข้อมูลน้อยลงมาก)

เมื่อเราวางไข่ด้วยProc::Asyncผลลัพธ์มาตรฐานของกระบวนการสร้างจะถูกผูกไว้กับท่อซึ่งไม่ใช่ TTY ดังนั้นโปรแกรมที่เรียกใช้อาจใช้สิ่งนี้เพื่อตัดสินใจใช้การบัฟเฟอร์เอาต์พุต

หากคุณยินดีที่จะมีการพึ่งพาอื่นคุณสามารถเรียกใช้โปรแกรมผ่านทาง. สิ่งที่ทำให้เกิด TTY เช่นunbuffer( ดูเหมือนเป็นส่วนหนึ่งของexpectแพ็คเกจ) นี่คือตัวอย่างของโปรแกรมที่มีปัญหาจากการบัฟเฟอร์:

my $proc = Proc::Async.new: 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
    .print
}

เราเห็นเพียง a 0แล้วต้องรอเป็นเวลานานเพื่อให้ได้ผลผลิตมากขึ้น เรียกใช้ผ่านunbuffer:

my $proc = Proc::Async.new: 'unbuffer', 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
    .print
}

หมายความว่าเราเห็นเอาต์พุตตัวเลขทุกวินาที

ราคุสามารถจัดหาโซลูชันในตัวให้กับวันนี้ได้หรือไม่? ใช่ - โดยการทำ "เวทมนตร์" ที่unbufferตัวเองทำ (ฉันคิดว่าจะจัดสรรptyTTY ปลอม) นี้ไม่ได้เป็นที่น่ารำคาญ - แม้ว่ามันจะถูกสำรวจโดยนักพัฒนา libuv ; อย่างน้อยที่สุดเท่าที่ Rakudo บน MoarVM ดำเนินไปในขณะที่มีการเปิดตัว libuv ซึ่งนำเสนอคุณลักษณะดังกล่าวเราจะดำเนินการเปิดเผย

6 ugexe Aug 19 2020 at 20:11

คุณสามารถตั้งค่า.out-bufferแฮนเดิล (เช่น$*OUTหรือ$*ERR) เป็น 0:

$ ./run raku -e '$*OUT.out-buffer = 0; react whenever Supply.interval: 1 { .say }'

PID: 11340
OUT: 0
OUT: 1
OUT: 2
OUT: 3
OUT: 4
Done