summaryrefslogtreecommitdiff
path: root/test/fiber/test_scheduler.rb
blob: 34effad816316a2a29670bd71b00e3c3de69d2e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# frozen_string_literal: true
require 'test/unit'
require_relative 'scheduler'

class TestFiberScheduler < Test::Unit::TestCase
  def test_fiber_without_scheduler
    # Cannot create fiber without scheduler.
    assert_raise RuntimeError do
      Fiber.schedule do
      end
    end
  end

  def test_fiber_new
    f = Fiber.new{}
    refute f.blocking?
  end

  def test_fiber_new_with_options
    f = Fiber.new(blocking: true){}
    assert f.blocking?

    f = Fiber.new(blocking: false){}
    refute f.blocking?

    f = Fiber.new(pool: nil){}
    refute f.blocking?
  end

  def test_fiber_blocking
    f = Fiber.new(blocking: false) do
      fiber = Fiber.current
      refute fiber.blocking?
      Fiber.blocking do |_fiber|
        assert_equal fiber, _fiber
        assert fiber.blocking?
      end
    end
    f.resume
  end

  def test_closed_at_thread_exit
    scheduler = Scheduler.new

    thread = Thread.new do
      Fiber.set_scheduler scheduler
    end

    thread.join

    assert scheduler.closed?
  end

  def test_closed_when_set_to_nil
    scheduler = Scheduler.new

    thread = Thread.new do
      Fiber.set_scheduler scheduler
      Fiber.set_scheduler nil

      assert scheduler.closed?
    end

    thread.join
  end

  def test_close_at_exit
    assert_in_out_err %W[-I#{__dir__} -], <<-RUBY, ['Running Fiber'], [], success: true
    require 'scheduler'
    Warning[:experimental] = false

    scheduler = Scheduler.new
    Fiber.set_scheduler scheduler

    Fiber.schedule do
      sleep(0)
      puts "Running Fiber"
    end
    RUBY
  end

  def test_minimal_interface
    scheduler = Object.new

    def scheduler.block
    end

    def scheduler.unblock
    end

    def scheduler.io_wait
    end

    def scheduler.kernel_sleep
    end

    thread = Thread.new do
      Fiber.set_scheduler scheduler
    end

    thread.join
  end

  def test_current_scheduler
    thread = Thread.new do
      scheduler = Scheduler.new
      Fiber.set_scheduler scheduler

      assert Fiber.scheduler
      refute Fiber.current_scheduler

      Fiber.schedule do
        assert Fiber.current_scheduler
      end
    end

    thread.join
  end

  def test_autoload
    10.times do
      Object.autoload(:TestFiberSchedulerAutoload, File.expand_path("autoload.rb", __dir__))

      thread = Thread.new do
        scheduler = Scheduler.new
        Fiber.set_scheduler scheduler

        10.times do
          Fiber.schedule do
            Object.const_get(:TestFiberSchedulerAutoload)
          end
        end
      end

      thread.join
    ensure
      $LOADED_FEATURES.delete(File.expand_path("autoload.rb", __dir__))
      Object.send(:remove_const, :TestFiberSchedulerAutoload)
    end
  end

  def test_deadlock
    mutex = Thread::Mutex.new
    condition = Thread::ConditionVariable.new
    q = 0.0001

    signaller = Thread.new do
      loop do
        mutex.synchronize do
          condition.signal
        end
        sleep q
      end
    end

    i = 0

    thread = Thread.new do
      scheduler = SleepingBlockingScheduler.new
      Fiber.set_scheduler scheduler

      Fiber.schedule do
        10.times do
          mutex.synchronize do
            condition.wait(mutex)
            sleep q
            i += 1
          end
        end
      end
    end

    # Wait for 10 seconds at most... if it doesn't finish, it's deadlocked.
    thread.join(10)

    # If it's deadlocked, it will never finish, so this will be 0.
    assert_equal 10, i
  ensure
    # Make sure the threads are dead...
    thread.kill
    signaller.kill
    thread.join
    signaller.join
  end
end