Comparing int to float types in a concurrent environment

protocol Banking{
    func paymentAmount(amount: Double) throws;
}
enum PaymentError: Error {
    case inSufficientAccountBalance
}
var accountBalance=30000.00
struct PayPal: Banking {
    func paymentAmount(amount: Double) throws {
        debugPrint("welcome to paypal payment gateway")
        guard accountBalance > amount else{ throw
            PaymentError.inSufficientAccountBalance
        }
        Thread.sleep(forTimeInterval: Double.random(in: 1...3))
        accountBalance -= amount
    }
    func printMessage(){
        debugPrint("Payment successful through paypal, new account balance=\(accountBalance)")
    }
}
struct Stripe: Banking {
    func paymentAmount(amount: Double) throws {
        debugPrint("welcome to Stripe payment gateway")
        guard accountBalance > amount else{ throw
            PaymentError.inSufficientAccountBalance
        }
        Thread.sleep(forTimeInterval: Double.random(in: 1...3))
        accountBalance -= amount
    }
    func printMessage(){
        debugPrint("Payment successful through Stripe, new account balance=\(accountBalance)")
    }
}
let queue=DispatchQueue(label: "just-a-queue", qos: .utility, attributes: .concurrent)
queue.async{
    do{
        let paypal=PayPal()
        try paypal.paymentAmount(amount: 10000)
        paypal.printMessage()
    }catch PaymentError.inSufficientAccountBalance {
        debugPrint("Payment failure due to insufficient Account Balance, paypal transaction cancelled")
    }catch{
        debugPrint("Error")
    }
}
queue.async{
    do{
        let stripe=Stripe()
        try stripe.paymentAmount(amount: 25000)
        stripe.printMessage()
        
    }catch PaymentError.inSufficientAccountBalance {
        debugPrint("Payment failure due to insufficient Account Balance, stripe transaction cancelled")
    }catch{
        debugPrint("Error")
    }
}

The above code have 2 structs PayPal and Stripe that conforms to banking and performs payment if the account balance is greater than amount to be paid. The final payment message that belongs to async block prints payment message for both the structs.
Here in this code I am intentionally trying to create a race condition by making payment from both the gateways. hence after PayPal async block is executed, the balance is 30000-10000=20000
After this, when stripe async block is executed, 20000-25000=-5000.
so the output comes out to be

"welcome to Stripe payment gateway"

"welcome to paypal payment gateway"

"Payment successful through paypal, new account balance=20000.0"

"Payment successful through Stripe, new account balance=-5000.0"

but some times the same code gives different output:

"welcome to paypal payment gateway"

"welcome to Stripe payment gateway"

"Payment successful through Stripe, new account balance=5000.0"

"Payment successful through paypal, new account balance=-5000.0"

Solution:
I think that I was passing an int type amount to be compared with the float type accountBalance.
When I changed the amount to be float type then the code gives the expected output as follows every time :

"welcome to Stripe payment gateway"

"welcome to paypal payment gateway"

"Payment successful through paypal, new account balance=20000.0"

"Payment successful through Stripe, new account balance=-5000.0"

I wanted to ask is there any concept behind the solution or any logic behind the scenes. Because if i normally compare int type to value type then it is surely going to give me an error but in a concurrent environment i am not sure, why does it even run the code for comparing int to float type and then performing execution of other following tasks.

Routine reminder: Real money isn't compatible with common floating point numbers — that combination is as dangerous as multithreading (but it's easy to fix ;-).

2 Likes

Just to clarify, I don't see anywhere that you're passing an Int type amount to compare with the float accountBalance (really, Double). What exactly was the change that you made—was it changing the values 10000 and 25000 to read 10000.0 and 25000.0, respectively? Swift is smart enough to know that 10000 (or any number without the decimal point) is intended to be a floating point number if it's used in a context that expects a floating point number (such as being passed to a function that accepts a Double, like you're doing with paymentAmount.

More broadly, the "different" output you have here doesn't seem surprising at all to me. Like you said, you've deliberately created a race condition by running these blocks concurrently—with a concurrent queue, you have (as far as I know) virtually no guarantees about execution order of work submitted to the queue, other than that earlier-submitted work (i.e., earlier calls to queue.async) will start execution before later-submitted work. But since the work is executing in parallel, and especially because you're Thread.sleeping for a random amount of time in both the PayPal and Stripe cases, the order in which the work finishes execution can easily change from run to run.

Could you elaborate on why the "different" output you've called out above was unexpected to you? Why do you think that output shouldn't be able to happen?

1 Like