Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Atomically { Delete Your Actors }

Moore's Law may be dead, but CPUs acquire more cores every year. If you want to minimize response latency in your application, you need to use every last core - without wasting resources that would destroy performance and throughput. Traditional locks grind threads to a halt and are prone to deadlocks, and although actors provide a saner alternative, they compose poorly and are at best weakly-typed.
In this presentation, created exclusively for Scalar Conf, John reveals a new alternative to the horrors of traditional concurrency, directly comparing it to other leading approaches in Scala. Witness the beauty, simplicity, and power of declarative concurrency, as you discover how functional programming is the most practical solution to solving the tough challenges of concurrency.

  • Login to see the comments

Atomically { Delete Your Actors }

  1. 1. atomically { delete your actors } John A. De Goes — @jdegoes Wiem Zine Elabidine — @wiemzin
  2. 2. The Bank Heist The Hero Become a Hero!
  3. 3. The Bank Heist
  4. 4. The Bank
  5. 5. #1
  6. 6. #1
  7. 7. 12 def transfer(from: Account, to: Account, amount: Amount): Unit = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } }
  8. 8. 13 def transfer(from: Account, to: Account, amount: Amount): Unit = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } }
  9. 9. 14 def transfer(from: Account, to: Account, amount: Amount): Unit = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } }
  10. 10. 15 def transfer(from: Account, to: Account, amount: Amount): Unit = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } }
  11. 11. Double-Spend Exploits $1 M $1 M $1 M $2 M
  12. 12. def transfer(from: Account, to: Account, amount: Amount) = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } } def transfer(from: Account, to: Account, amount: Amount) = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } } Thread 1 Thread 2 Race Condition Exploit
  13. 13. def transfer(from: Account, to: Account, amount: Amount) = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } } def transfer(from: Account, to: Account, amount: Amount) = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } } Thread 1 Thread 2 Race Condition Exploit
  14. 14. 20 class Account { var balance : Amount var accountID : AccountID var name : String val opened : Instant val status : AccountStatus val tpe : AccountType }
  15. 15. balance 200,000 accountID 2816157231 ... ... Main Memory balance 200,000 accountID 2816157231 ... ... Core 1 Cache balance 200,000 accountID 2816157231 ... ... Core 2 Cache
  16. 16. def transfer(from: Account, to: Account, amount: Amount) = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } } def transfer(from: Account, to: Account, amount: Amount) = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } } Thread 1 Thread 2 Stale Cache Exploit
  17. 17. from.balance -= amount 4: getfield #2 7: invokevirtual #3 10: aload_2 11: invokevirtual #3 14: isub 15: invokestatic #4 18: dup_x1 19: putfield #2
  18. 18. def transfer(from: Account, to: Account, amount: Amount) = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } } def transfer(from: Account, to: Account, amount: Amount) = { if (from.balance >= amount) { from.balance -= amount to.balance += amount } else { Thread.sleep(100) transfer(from, to, amount) } } Thread 1 Thread 2 Nonatomic Instruction Exploit
  19. 19. abstract class Account { trait State { @volatile var balance: BigDecimal = _ } val state: State def modify[A](f: State => A): A = this.synchronized { val result = f(state) this.notifyAll() result } def await(): Unit = this.synchronized { this.wait() } }
  20. 20. def transfer(from: Account, to: Account, amount: Amount): Unit = { var loop = true while (loop) { from.modify { state => if (state.balance >= amount.value) { state.balance -= amount.value to.modify(state => state.balance += amount.value) loop = false } else from.await() } }
  21. 21. transfer(from, to, amount) transfer(to, from, amount) Thread 1 Thread 2 Deadlock Exploit
  22. 22. class Account extends Actor { var balance = Amount.zero var todos = List.empty[(ActorRef, Message)] def receive = { case Deposit(amount) => balance = balance + amount.value sender ! Success(balance) todos.foreach { case (s, m) => self ! m } todos = Nil case v @ Withdraw(amount) => if (balance >= amount.value) { balance = balance - amount.value sender ! Success(balance) } else todos = (sender, v) :: todos case Balance => sender ! Success(balance) } }
  23. 23. def transfer(from: ActorRef, to: ActorRef, amount: Amount)(implicit garbage: Timeout): Unit = (from ? Withdraw(amount)).flatMap { _ => to ? Deposit(amount) }
  24. 24. for { john <- johnAccount ? Balance wiem <- wiemAccount ? Balance } yield if (wiem.value > john.value) transfer(wiem, cafe, amount) else transfer(john, cafe, amount) $70 $99
  25. 25. for { john <- johnAccount ? Balance wiem <- wiemAccount ? Balance } yield if (wiem.value > john.value) transfer(wiem, cafe, amount) else transfer(john, cafe, amount) $70 $6,000,000 $99
  26. 26. for { john <- johnAccount ? Balance wiem <- wiemAccount ? Balance } yield if (wiem.value > john.value) transfer(wiem, cafe, amount) else transfer(john, cafe, amount) $70 $6,000,000 $99
  27. 27. $70 ~$6m for { john <- johnAccount ? Balance wiem <- wiemAccount ? Balance } yield if (wiem.value > john.value) transfer(wiem, cafe, amount) else transfer(john, cafe, amount) $10 $99
  28. 28. $70 -$60 for { john <- johnAccount ? Balance wiem <- wiemAccount ? Balance } yield if (wiem.value > john.value) transfer(wiem, cafe, amount) else transfer(john, cafe, amount) $99
  29. 29. 46 def transfer(from : TRef[Account], to : TRef[Account], amount : Amount): UIO[Unit] = atomically { for { balance <- from.get _ <- check(balance >= amount) _ <- from.update(_ - amount) _ <- to.update(_ + amount) } yield () }
  30. 30. 47 def transfer(from : TRef[Account], to : TRef[Account], amount : Amount): UIO[Unit] = atomically { for { balance <- from.get _ <- check(balance >= amount) _ <- from.update(_ - amount) _ <- to.update(_ + amount) } yield () }
  31. 31. 48 def transfer(from : TRef[Account], to : TRef[Account], amount : Amount): UIO[Unit] = atomically { for { balance <- from.get _ <- check(balance >= amount) _ <- from.update(_ - amount) _ <- to.update(_ + amount) } yield () }
  32. 32. 49 def transfer(from : TRef[Account], to : TRef[Account], amount : Amount): UIO[Unit] = atomically { for { balance <- from.get _ <- check(balance >= amount) _ <- from.update(_ - amount) _ <- to.update(_ + amount) } yield () }
  33. 33. 50 def transfer(from : TRef[Account], to : TRef[Account], amount : Amount): UIO[Unit] = atomically { for { balance <- from.get _ <- check(balance >= amount) _ <- from.update(_ - amount) _ <- to.update(_ + amount) } yield () }
  34. 34. 51 def transfer(from : TRef[Account], to : TRef[Account], amount : Amount): UIO[Unit] = atomically { for { balance <- from.get _ <- check(balance >= amount) _ <- from.update(_ - amount) _ <- to.update(_ + amount) } yield () }
  35. 35. The Hero
  36. 36. STM: Software Transactional Memory Provides the ability to atomically commit a series of reads and writes to transactional memory when a set of conditions is satisfied. STM
  37. 37. STM ...Op 2Op 1 Op n Final State Initial State commit failure - rollback retry - rollback complete complete
  38. 38. STM STM[E, A] A transaction, which models reads & writes, and can fail, retry, or succeed. TRef[A] A transactional reference, which is read & written inside STM transactions.
  39. 39. STM[E, A] Succeed with a value of type A Fail with an error of type E STM
  40. 40. STM STM[E, A] A transaction, which models reads & writes, and can fail, retry, or succeed. TRef[A] A transactional reference, which is read & written inside STM transactions.
  41. 41. TRef[A] An immutable value of type A STM
  42. 42. STM STM STM STM Composable
  43. 43. trait TRef[A] { val get: STM[Nothing, A] def set(newValue: A): STM[Nothing, Unit] def update(f: A => A): STM[Nothing, A] def modify[B](f: A => (B, A)): STM[Nothing, B] } object TRef { def make[A](a: A): STM[Nothing, TRef[A]] } TRef
  44. 44. trait TRef[A] { val get: STM[Nothing, A] def set(newValue: A): STM[Nothing, Unit] def update(f: A => A): STM[Nothing, A] def modify[B](f: A => (B, A)): STM[Nothing, B] } object TRef { def make[A](a: A): STM[Nothing, TRef[A]] } TRef
  45. 45. trait TRef[A] { val get: STM[Nothing, A] def set(newValue: A): STM[Nothing, Unit] def update(f: A => A): STM[Nothing, A] def modify[B](f: A => (B, A)): STM[Nothing, B] } object TRef { def make[A](a: A): STM[Nothing, TRef[A]] } TRef
  46. 46. trait TRef[A] { val get: STM[Nothing, A] def set(newValue: A): STM[Nothing, Unit] def update(f: A => A): STM[Nothing, A] def modify[B](f: A => (B, A)): STM[Nothing, B] } object TRef { def make[A](a: A): STM[Nothing, TRef[A]] } TRef
  47. 47. trait TRef[A] { val get: STM[Nothing, A] def set(newValue: A): STM[Nothing, Unit] def update(f: A => A): STM[Nothing, A] def modify[B](f: A => (B, A)): STM[Nothing, B] } object TRef { def make[A](a: A): STM[Nothing, TRef[A]] } TRef
  48. 48. trait STM[+E, +A] { def commit: IO[E, A] = STM.atomically { this } } object STM { def atomically[E, A](stm: STM[E, A]): IO[E, A] } STM - Commit
  49. 49. val hello: STM[Nothing, String] = STM.succeed("Welcome to Scalar") STM - Succeed
  50. 50. val sumBalances: STM[Nothing, Int] = balance1.get.flatMap(a => balance2.get.map(b => a + b)) STM - map & flatMap
  51. 51. val sumBalances: STM[Nothing, Int] = for { a <- balance1.get b <- balance2.get } yield a + b STM - map & flatMap
  52. 52. STM - Fail def debit(sender: TRef[Amount], amount: Amount): STM[String, Amount] = for { balance <- sender.update(_ - amount) _ <- if (balance < 0) STM.fail("Insufficient funds") else STM.succeed(()) } yield balance
  53. 53. def debitSuccess(sender: TRef[Amount], amount: Amount): STM[Nothing, Boolean] = debit(sender, amount).fold(_ => false, _ => true) STM - Fold
  54. 54. def debitWithBalance(sender: TRef[Amount], amount: Amount): STM[Nothing, Amount] = debit(sender, amount) .foldM( _ => sender.get, _ => sender.get) STM - FoldM
  55. 55. def awaitTicket(tref: TRef[Option[Ticket]]): STM[Nothing, Ticket] = for { option <- tref.get ticket <- option match { case None => STM.retry case Some(ticket) => STM.succeed(ticket) } } yield ticket STM - Retry
  56. 56. def tripTickets: STM[Nothing, (Ticket, Ticket)] = awaitTicket(toWarsaw) zip awaitTicket(toHome) STM - Zip
  57. 57. def bid(price : Amount, org : TRef[TravelCompany]): STM[Nothing, Ticket] = for { v <- org.get _ <- STM.check(v.availTix.exists(_.price <= price)) ticket = findCheapest(v, price) _ <- org.update(_.removeTix(ticket)) } yield ticket STM - Check
  58. 58. STM - Filter def bid(price : Amount, org : TRef[TravelCompany]): STM[Nothing, Ticket] = for { v <- org.get.filter(_.availTix.exists(_.price <= price)) ticket = findCheapest(v, price) _ <- org.update(_.removeTix(ticket)) } yield ticket
  59. 59. val trainOrAirplane: STM[Nothing, Ticket] = bid(25.00, Railway) orElse bid(50.00, Airline) STM - Choice
  60. 60. def checkIn(passengers: TRef[List[Person]]): STM[Nothing, Person] = for { head <- passengers.get.collect { case head :: tail => head } _ <- passengers.update(_.drop(1)) } yield head STM - Collect
  61. 61. STM STM STM STM Composable Easy to Reason About def transfer(from : TRef[Account], to : TRef[Account], amount : Amount): UIO[Unit] = atomically { for { balance <- from.get _ <- check(balance >= amount) _ <- from.update(_ - amount) _ <- to.update(_ + amount) } yield () }
  62. 62. Become a Hero
  63. 63. Semaphore Thread #2 holds 1 permit Thread #1 holds 2 permits Thread #3 waits for 4 permits Semaphore 6 permits
  64. 64. Semaphore 7 contributors 11 months+ 301 lines of code
  65. 65. Semaphore type Semaphore = TRef[Int]
  66. 66. Semaphore def makeSemaphore(n: Int): UIO[Semaphore] = TRef.make(n).commit
  67. 67. Semaphore def acquire(semaphore: Semaphore, n: Int): UIO[Unit] = (for { value <- semaphore.get _ <- STM.check(value >= n) _ <- semaphore.set(value - n) } yield ()).commit
  68. 68. Semaphore def acquire(semaphore: Semaphore, n: Int): UIO[Unit] = (for { value <- semaphore.get _ <- STM.check(value >= n) _ <- semaphore.set(value - n) } yield ()).commit
  69. 69. Semaphore def acquire(semaphore: Semaphore, n: Int): UIO[Unit] = (for { value <- semaphore.get _ <- STM.check(value >= n) _ <- semaphore.set(value - n) } yield ()).commit
  70. 70. Semaphore def acquire(semaphore: Semaphore, n: Int): UIO[Unit] = (for { value <- semaphore.get _ <- STM.check(value >= n) _ <- semaphore.set(value - n) } yield ()).commit
  71. 71. Semaphore def release(semaphore: Semaphore, n: Int): UIO[Unit] = semaphore.update(_ + n).commit
  72. 72. Semaphore 1 author (you!) 10 minutes 8 lines of code Your Semaphore using ZIO!
  73. 73. Promise Unset Set Wait Wait Continue Continue
  74. 74. Promise 7 contributors 11 months+ 274 lines of code
  75. 75. Promise type Promise[A] = TRef[Option[A]]
  76. 76. Promise def makePromise[A]: UIO[Promise[A]] = TRef.make(None).commit
  77. 77. Promise def complete[A](promise: Promise[A], v: A): UIO[Boolean] = (for { value <- promise.get change <- value match { case Some(_) => STM.succeed(false) case None => promise.set(Some(v)) *> STM.succeed(true) } } yield change).commit
  78. 78. Promise def complete[A](promise: Promise[A], v: A): UIO[Boolean] = (for { value <- promise.get change <- value match { case Some(_) => STM.succeed(false) case None => promise.set(Some(v)) *> STM.succeed(true) } } yield change).commit
  79. 79. Promise def complete[A](promise: Promise[A], v: A): UIO[Boolean] = (for { value <- promise.get change <- value match { case Some(_) => STM.succeed(false) case None => promise.set(Some(v)) *> STM.succeed(true) } } yield change).commit
  80. 80. Promise def complete[A](promise: Promise[A], v: A): UIO[Boolean] = (for { value <- promise.get change <- value match { case Some(_) => STM.succeed(false) case None => promise.set(Some(v)) *> STM.succeed(true) } } yield change).commit
  81. 81. Promise def await[A](promise: Promise[A]): UIO[A] = promise.get.collect { case Some(a) => a }.commit
  82. 82. Promise 1 author (you!) 10 minutes 8 lines of code Your Promise using ZIO!
  83. 83. Queue Empty Queue Capacity: 6 Full Queue Capacity: 6 Offer (Continue) Take (Wait) Offer (Wait) Take (Continue)
  84. 84. Queue 4 contributors 9 months+ 487 lines of code
  85. 85. Queue case class Queue[A]( capacity : Int, tref : TRef[ScalaQueue[A]])
  86. 86. Queue def makeQueue[A](capacity: Int): UIO[Queue[A]] = TRef.make(ScalaQueue.empty[A]).commit .map(Queue(capacity, _))
  87. 87. Queue def offer[A](queue: Queue[A], a: A): UIO[Unit] = (for { q <- queue.tref.get _ <- STM.check(q.length < queue.capacity) _ <- queue.tref.update(_ enqueue a) } yield ()).commit
  88. 88. Queue def offer[A](queue: Queue[A], a: A): UIO[Unit] = (for { q <- queue.tref.get _ <- STM.check(q.length < queue.capacity) _ <- queue.tref.update(_ enqueue a) } yield ()).commit
  89. 89. Queue def offer[A](queue: Queue[A], a: A): UIO[Unit] = (for { q <- queue.tref.get _ <- STM.check(q.length < queue.capacity) _ <- queue.tref.update(_ enqueue a) } yield ()).commit
  90. 90. Queue def offer[A](queue: Queue[A], a: A): UIO[Unit] = (for { q <- queue.tref.get _ <- STM.check(q.length < queue.capacity) _ <- queue.tref.update(_ enqueue a) } yield ()).commit
  91. 91. Queue def take[A](queue: Queue[A]): UIO[A] = (for { q <- queue.tref.get a <- q.dequeueOption match { case Some((a, as)) => queue.tref.set(as) *> STM.succeed(a) case _ => STM.retry } } yield a).commit
  92. 92. Queue def take[A](queue: Queue[A]): UIO[A] = (for { q <- queue.tref.get a <- q.dequeueOption match { case Some((a, as)) => queue.tref.set(as) *> STM.succeed(a) case _ => STM.retry } } yield a).commit
  93. 93. Queue def take[A](queue: Queue[A]): UIO[A] = (for { q <- queue.tref.get a <- q.dequeueOption match { case Some((a, as)) => queue.tref.set(as) *> STM.succeed(a) case _ => STM.retry } } yield a).commit
  94. 94. Queue def take[A](queue: Queue[A]): UIO[A] = (for { q <- queue.tref.get a <- q.dequeueOption match { case Some((a, as)) => queue.tref.set(as) *> STM.succeed(a) case _ => STM.retry } } yield a).commit
  95. 95. Queue 1 author (you) 14 minutes 13 lines of code Your Queue using ZIO!
  96. 96. Wrap Up
  97. 97. THANK YOU! LEARN MORE github.com/scalaz/scalaz-zio gitter.im/scalaz/scalaz-zio FOLLOW US @jdegoes @wiemzin

×