I’ve run into a problem with the bank-account exercise on my local machine running crystal 1.21.0 (linux x86-64).
A straightforward implementation without any regards to multi-whatever (basically this one) failed on the final test, so I looked at the spec for a hint what might go wrong.
it "Can handle concurrent transactions" do
bank_account = BankAccount.new
bank_account.open
channel = Channel(Int32).new
1000.times do
spawn do
begin
bank_account.deposit(1)
bank_account.balance.should eq 1
channel.send(0)
rescue
channel.send(1)
end
end
spawn do
begin
bank_account.withdraw(1)
bank_account.balance.should eq 0
channel.send(0)
rescue exception
channel.send(1)
end
end
channel.receive.should eq 0
end
1000.times { Fiber.yield }
bank_account.balance.should eq 0
end
Each iteration spawns two fibers, each fiber performs a channel.send. After two sends per iteration only one receive is performed. Looked wrong, so I duplicated the line channel.receive.should eq 0 and now the solution passed again.
If there are two receives, the code passes (even for 10 million iterations), if there is only one receive, the code starts failing somewhere between 250 and 500 iterations.
So I added some pp to the rescue statements and got the somewhat expected mismatch between expected and actual balance.
#<Spec::AssertionFailed:Expected: 1
got: 2>
#<Spec::AssertionFailed:Expected: 0
got: 1>
...
The same happens for “fancy” solutions using Mutex (example): It fails with a single receive, it passes with two receives.
So, to paraphrase Inigo Montoya, “I do not think that this test is testing what you think it tests.”