Bank-account failures on crystal 1.21.0

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.”