Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
bcpeinhardt committed May 21, 2024
1 parent 183f864 commit 681c231
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 37 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ If you haven't heard:
languages are fundamentally different, concurrency is handled at the library level.
- Erlang is sorta famous for its concurrency model, as well as it's standard framework for building
concurrent applications: OTP. (Note: This isn't research stuff, Erlang is extremely battle tested.
It runs a lot of places, and it was estimated in 2019 that "... 90% of all internet traffic going through routers and switches controlled by Erlang." [source](https://www.erlang-solutions.com/blog/which-companies-are-using-erlang-and-why-mytopdogstatus/))
It runs a lot of places, and it was estimated in 2019 that "... 90% of all internet traffic (was) going through routers and switches controlled by Erlang." [source](https://www.erlang-solutions.com/blog/which-companies-are-using-erlang-and-why-mytopdogstatus/))
- Gleam has a package for using the OTP framework, called `gleam_otp`. It also has a package for more fundamental
Erlang specific concepts, `gleam_erlang`.

I want to use these to build bad ass fault tolerant concurrent software!! But first, I need to learn OTP,
which means I'll be learning with resources written in Erlang (one could also choose Elixir, but I'm #notlikeotherdevs).
which means I'll be learning with resources written in Erlang and Elixir (neither of which are languages I'm overly experienced with if I'm honest).

While I go on this journey, I'm going to do my damndest to translate the things I learn to Gleam, and record
them here, so that in the future this repo might serve as the base for learning OTP with Gleam.
Expand All @@ -31,12 +31,12 @@ This resource presumes ALOT of prerequisite knowledge in it's users.
to learn. If you have never seen Gleam, try out the [interactive tour](https://tour.gleam.run/), or follow
the [Syllabus on Exercism](https://exercism.org/tracks/gleam/concepts).

Each section is broken into it's own module, some sections with submodules. You can read them in the order
Each section is broken into it's own module (a top level file named <module_name>.gleam, and optionally a folder named <module_name> containing files for submodules). You can read them in the order
you like, but I recommend
1. concurrency_primitives
2. tasks
3. actors
4. supervisors
1. concurrency_primitives
2. tasks
3. actors
4. supervisors

All the code in this project is runnable, just run `gleam run -m <module_name>`. Feel free to clone the repo
and tinker with the code!
Expand Down
5 changes: 4 additions & 1 deletion src/concurrency_primitives.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub fn main() {
// The catch is that selecting from a selector has to produce only one type of message, so you'll
// need to map the messages to a common type.
// In this example, I want to receive messages as strings, so I tell the selector to turn subject 1's
// messages into string using `int.to_string`, and to leave subject 2's messages alone
// messages into a string using `int.to_string`, and to leave subject 2's messages alone
// using `function.identity`.

// Try sending the messages in different order to see the selector in action!
Expand All @@ -134,4 +134,7 @@ pub fn main() {
io.println("Received: " <> some_str)
let assert Ok(some_str_2) = process.select(selector, 1000)
io.println("Received: " <> some_str_2)

// Hopefully this introduction made sense. If you're reading in the recommended order,
// head over to tasks.gleam next.
}
34 changes: 17 additions & 17 deletions src/tasks.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub fn main() {
// Note that this task is eager, it will start immediately
let handle =
task.async(fn() {
process.sleep(1000)
process.sleep(500)
io.println("Task is done")
})

Expand All @@ -34,30 +34,27 @@ pub fn main() {
io.println("I won't execute until the task is done.")

// There's a problem with the code above. If the task times out,
// the current process will panic! Most of the time, you don't want that.
// the current process will crash! Most of the time, you don't want that.
// You can use `task.try_await` to handle the timeout gracefully.

let handle = task.async(fn() { process.sleep(2000) })
let handle = task.async(fn() { process.sleep(1000) })

case task.try_await(handle, 1000) {
case task.try_await(handle, 500) {
Ok(_) -> io.println("Task finished successfully")
// This is the one that will execute
Error(_) -> io.println("Task timed out!")
}

// By default task processes are "linked" to the current process.
// This is a bidirectional link:
// If the current process panics and shuts down, the task will too.
// If the task panics and shuts down, the current process will too!.
//
// FOOTGUN ALERT!: `task.try_await` will only protect you from a timeout.
// If the task panics, the current process will panic too!
// FOOTGUN ALERT!: `task.try_await` will only protect you from a timeout or a "crash" (the process exiting).
// If the task panics (which is subtly different than an exit), the current process will panic too!
//
// If you're thinking "That's kinda shit, what if I want to handle panicked
// processes gracefully?", well, OTP has amazing constructs for that
// processes gracefully as well?", well, OTP has amazing constructs for that
// called supervisors. It'd rather you use those than roll your own
// shitty version.
//
// If you REALLY want to handle crashing tasks yourself, there's a function
// If you REALLY want to handle panicking tasks yourself, there's a function
// called [rescue](https://hexdocs.pm/gleam_erlang/gleam/erlang.html#rescue)
// which takes any function and converts a panic into a result.
// It's really meant for handling exceptions thrown in ffi erlang/elixir
Expand All @@ -67,9 +64,6 @@ pub fn main() {
let assert Error(_) = erlang.rescue(fn() { panic })

// And an example using it with tasks, shame on you for ignoring my sage advice.
// See how I used `erlang.rescue` on the very inside? That's important.
// Calling `task.await` on a process that panics will generate a timeout,
// which will crash everything anyway.
let assert Error(_) =
task.await(task.async(fn() { erlang.rescue(fn() { panic }) }), 1000)

Expand All @@ -91,6 +85,8 @@ pub fn main() {
// appears. We use a list of codepoints instead of a string because dealing with graphemes
// properly just distracts from the point of this exercise.

// Sidenote: If Gleam error handling is confusing for you, I've written [a short blog
// post on the subject](https://www.benjaminpeinhardt.com/error-handling-in-gleam/)
use workload <- result.try(simplifile.read("./src/tasks/king_james_bible.txt"))
let workload = string.to_utf_codepoints(workload)

Expand Down Expand Up @@ -147,8 +143,9 @@ fn parallel_letter_frequency(
// We fold over the partial mapping we got back from the task
// and update the total count with it.
//
// Notice we do this inside the fold of the tasks. We don't want to
// await the next task until we're out of work to do.
// Notice we do this inside the fold of the task handles rather than in another loop.
// We don't want to await the next task until we're out of work to do.

use total_freq, letter, count <- dict.fold(partial_freq, total_freq)
use entry <- dict.update(total_freq, letter)
case entry {
Expand All @@ -166,3 +163,6 @@ fn time(name: String, f: fn() -> a) -> a {
io.println(name <> " took: " <> int.to_string(difference) <> "ms")
x
}

// Alright, if you made it through all this, head on over to actors.gleam to see how more
// long running concurrent operations are handled.
12 changes: 0 additions & 12 deletions test/concurrency_primatives_test.gleam

This file was deleted.

0 comments on commit 681c231

Please sign in to comment.