Skip to content

Commit 320ce97

Browse files
Experimental support for async-safe.
1 parent dd1bd32 commit 320ce97

File tree

6 files changed

+76
-0
lines changed

6 files changed

+76
-0
lines changed

config/sus.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@
55

66
require "covered/sus"
77
include Covered::Sus
8+
9+
require "async/safe"
10+
Async::Safe.enable!

examples/async-safe/test.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../../lib/io/stream"
5+
6+
require "async"
7+
require "async/safe"
8+
9+
# Enable concurrent access detection:
10+
Async::Safe.enable!
11+
12+
# Create a simple stream with some data:
13+
input, output = IO.pipe
14+
stream = IO::Stream::Buffered.new(input)
15+
output.write("Hello")
16+
17+
Async do |task|
18+
task.async(transient: true) do
19+
data = stream.read(10)
20+
end
21+
22+
# This should raise ViolationError:
23+
stream.read(10)
24+
end

gems.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@
3131

3232
gem "sus-fixtures-async"
3333
gem "sus-fixtures-openssl"
34+
35+
gem "async-safe"
3436
end

lib/io/stream/generic.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ class Generic
1818
include Readable
1919
include Writable
2020

21+
# Check if a method is async-safe.
22+
#
23+
# @parameter method [Symbol] The method name to check.
24+
# @returns [Symbol | Boolean] The concurrency guard for the given method.
25+
def self.async_safe?(method)
26+
Readable.async_safe?(method) || Writable.async_safe?(method)
27+
end
28+
2129
# Initialize a new generic stream.
2230
# @parameter options [Hash] Options passed to included modules.
2331
def initialize(**options)

lib/io/stream/readable.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@ module IO::Stream
2525
#
2626
# You must implement the `sysread` method to read data from the underlying IO.
2727
module Readable
28+
ASYNC_SAFE = {
29+
read: :readable,
30+
read_partial: :readable,
31+
read_exactly: :readable,
32+
read_until: :readable,
33+
peek: :readable,
34+
gets: :readable,
35+
getc: :readable,
36+
getbyte: :readable,
37+
readline: :readable,
38+
readlines: :readable,
39+
readable?: :readable,
40+
fill_read_buffer: :readable,
41+
eof?: :readable,
42+
finished?: :readable,
43+
}.freeze
44+
45+
# Check if a method is async-safe.
46+
#
47+
# @parameter method [Symbol] The method name to check.
48+
# @returns [Symbol | Boolean] The concurrency guard for the given method.
49+
def self.async_safe?(method)
50+
ASYNC_SAFE.fetch(method, false)
51+
end
52+
2853
# Initialize readable stream functionality.
2954
# @parameter minimum_read_size [Integer] The minimum size for read operations.
3055
# @parameter maximum_read_size [Integer] The maximum size for read operations.

lib/io/stream/writable.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ module IO::Stream
1313
#
1414
# You must implement the `syswrite` method to write data to the underlying IO.
1515
module Writable
16+
ASYNC_SAFE = {
17+
write: true,
18+
puts: true,
19+
flush: true,
20+
}.freeze
21+
22+
# Check if a method is async-safe.
23+
#
24+
# @parameter method [Symbol] The method name to check.
25+
# @returns [Symbol | Boolean] The concurrency guard for the given method.
26+
def self.async_safe?(method)
27+
ASYNC_SAFE.fetch(method, false)
28+
end
29+
1630
# Initialize writable stream functionality.
1731
# @parameter minimum_write_size [Integer] The minimum buffer size before flushing.
1832
def initialize(minimum_write_size: MINIMUM_WRITE_SIZE, **, &block)

0 commit comments

Comments
 (0)