From 6345926c43d3cdd7bbdda869d0b4ee9ef4370244 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 11 Aug 2026 21:57:18 +1200 Subject: [PATCH] Initial presentation. --- presentation/bake.rb | 0 presentation/gems.locked | 177 +++++ presentation/gems.rb | 6 + presentation/public/_static/custom.css | 703 ++++++++++++++++++ presentation/readme.md | 28 + presentation/slides/010-welcome.md | 13 + .../slides/020-scalability/010-scalability.md | 13 + .../slides/020-scalability/020-latency-ns.md | 55 ++ .../slides/020-scalability/030-latency-us.md | 55 ++ .../slides/020-scalability/040-latency-ms.md | 51 ++ .../020-scalability/050-latency-coffee.md | 46 ++ .../060-waiting-for-information.md | 14 + .../slides/030-execution/010-execution.md | 15 + .../slides/030-execution/020-asynchrony.md | 19 + .../030-execution/025-asynchrony-diagram.md | 51 ++ .../slides/030-execution/030-parallelism.md | 17 + .../030-execution/035-parallelism-diagram.md | 47 ++ .../slides/030-execution/040-concurrency.md | 19 + .../030-execution/045-concurrency-diagram.md | 49 ++ .../030-execution/050-execution-tradeoffs.md | 14 + .../060-asynchronous-coordination.md | 14 + .../slides/040-waiting/010-request-latency.md | 35 + .../slides/040-waiting/020-ruby-faster.md | 12 + .../030-request-latency-optimized.md | 36 + .../040-waiting/040-waiting-dominates.md | 14 + presentation/slides/050-falcon/010-falcon.md | 14 + .../slides/050-falcon/020-falcon-server.md | 12 + .../slides/050-falcon/030-falcon-idea.md | 12 + .../050-falcon/040-falcon-concurrency.md | 54 ++ .../slides/050-falcon/060-falcon-execution.md | 14 + presentation/slides/060-async/010-async.md | 14 + presentation/slides/060-async/020-io-wait.md | 12 + .../060-async/030-concurrent-requests.md | 26 + .../slides/060-async/040-request-a.md | 27 + .../slides/060-async/050-request-b.md | 27 + .../slides/060-async/060-wait-for-results.md | 27 + presentation/slides/060-async/070-io-event.md | 12 + .../slides/060-async/080-scheduler.md | 27 + .../slides/060-async/090-scheduler-wait.md | 27 + .../slides/060-async/100-event-loop.md | 27 + .../slides/060-async/110-async-result.md | 12 + 41 files changed, 1847 insertions(+) create mode 100644 presentation/bake.rb create mode 100644 presentation/gems.locked create mode 100644 presentation/gems.rb create mode 100644 presentation/public/_static/custom.css create mode 100644 presentation/readme.md create mode 100644 presentation/slides/010-welcome.md create mode 100644 presentation/slides/020-scalability/010-scalability.md create mode 100644 presentation/slides/020-scalability/020-latency-ns.md create mode 100644 presentation/slides/020-scalability/030-latency-us.md create mode 100644 presentation/slides/020-scalability/040-latency-ms.md create mode 100644 presentation/slides/020-scalability/050-latency-coffee.md create mode 100644 presentation/slides/020-scalability/060-waiting-for-information.md create mode 100644 presentation/slides/030-execution/010-execution.md create mode 100644 presentation/slides/030-execution/020-asynchrony.md create mode 100644 presentation/slides/030-execution/025-asynchrony-diagram.md create mode 100644 presentation/slides/030-execution/030-parallelism.md create mode 100644 presentation/slides/030-execution/035-parallelism-diagram.md create mode 100644 presentation/slides/030-execution/040-concurrency.md create mode 100644 presentation/slides/030-execution/045-concurrency-diagram.md create mode 100644 presentation/slides/030-execution/050-execution-tradeoffs.md create mode 100644 presentation/slides/030-execution/060-asynchronous-coordination.md create mode 100644 presentation/slides/040-waiting/010-request-latency.md create mode 100644 presentation/slides/040-waiting/020-ruby-faster.md create mode 100644 presentation/slides/040-waiting/030-request-latency-optimized.md create mode 100644 presentation/slides/040-waiting/040-waiting-dominates.md create mode 100644 presentation/slides/050-falcon/010-falcon.md create mode 100644 presentation/slides/050-falcon/020-falcon-server.md create mode 100644 presentation/slides/050-falcon/030-falcon-idea.md create mode 100644 presentation/slides/050-falcon/040-falcon-concurrency.md create mode 100644 presentation/slides/050-falcon/060-falcon-execution.md create mode 100644 presentation/slides/060-async/010-async.md create mode 100644 presentation/slides/060-async/020-io-wait.md create mode 100644 presentation/slides/060-async/030-concurrent-requests.md create mode 100644 presentation/slides/060-async/040-request-a.md create mode 100644 presentation/slides/060-async/050-request-b.md create mode 100644 presentation/slides/060-async/060-wait-for-results.md create mode 100644 presentation/slides/060-async/070-io-event.md create mode 100644 presentation/slides/060-async/080-scheduler.md create mode 100644 presentation/slides/060-async/090-scheduler-wait.md create mode 100644 presentation/slides/060-async/100-event-loop.md create mode 100644 presentation/slides/060-async/110-async-result.md diff --git a/presentation/bake.rb b/presentation/bake.rb new file mode 100644 index 00000000..e69de29b diff --git a/presentation/gems.locked b/presentation/gems.locked new file mode 100644 index 00000000..1fa2d1c4 --- /dev/null +++ b/presentation/gems.locked @@ -0,0 +1,177 @@ +GEM + remote: https://rubygems.org/ + specs: + agent-context (0.3.0) + bake (>= 0.23) + async (2.44.1) + console (~> 1.29) + fiber-annotation + io-event (~> 1.11) + async-actor (0.2.0) + async (~> 2.33) + async-container (0.38.0) + async (~> 2.44) + async-http (0.99.0) + async (>= 2.35.1) + async-pool (~> 0.11) + io-endpoint (~> 0.14) + io-stream (~> 0.14) + protocol-http (~> 0.66) + protocol-http1 (~> 0.39) + protocol-http2 (~> 0.26) + protocol-url (~> 0.2) + async-http-cache (0.4.6) + async-http (~> 0.56) + async-htty (0.5.0) + async (~> 2.39) + async-http (~> 0.88) + protocol-http (~> 0.62) + protocol-htty (~> 0.4) + async-pool (0.11.2) + async (>= 2.0) + async-service (0.25.0) + async + async-container (~> 0.34) + string-format (~> 0.2) + async-utilization (0.4.0) + console (~> 1.0) + async-webdriver (0.12.2) + async-actor (~> 0.1) + async-http (~> 0.61) + async-pool (~> 0.4) + async-websocket (~> 0.25) + base64 (~> 0.2) + async-websocket (0.30.1) + async-http (~> 0.76) + protocol-http (~> 0.34) + protocol-rack (~> 0.7) + protocol-websocket (~> 0.17) + bake (0.25.0) + bigdecimal + samovar (~> 2.1) + base64 (0.3.0) + bigdecimal (4.1.2) + console (1.37.0) + fiber-annotation + fiber-local (~> 1.1) + json + falcon (0.57.0) + async + async-container (~> 0.20) + async-http (~> 0.97) + async-http-cache (~> 0.4) + async-service (~> 0.19) + async-utilization (~> 0.3) + bundler + localhost (~> 1.1) + openssl (>= 3.0) + protocol-http (~> 0.31) + protocol-rack (~> 0.7) + samovar (~> 2.3) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.1) + io-endpoint (0.17.2) + io-event (1.19.4) + io-stream (0.14.0) + openssl (>= 3.3) + io-watch (0.6.3) + json (2.21.2) + live (0.18.2) + async-websocket (~> 0.27) + protocol-websocket (~> 0.19) + xrb (~> 0.10) + lively (0.18.0) + agent-context + async-htty + async-service (~> 0.23) + falcon (~> 0.47) + io-watch + live (~> 0.18) + xrb + localhost (1.8.0) + bake + markly (0.16.0) + openssl (4.0.2) + presently (0.16.0) + async-webdriver (~> 0.12) + lively (~> 0.18) + markly (~> 0.16) + protocol-hpack (1.5.1) + protocol-http (0.70.0) + protocol-http1 (0.40.2) + protocol-http (~> 0.68) + protocol-http2 (0.26.2) + protocol-hpack (~> 1.4) + protocol-http (~> 0.62) + protocol-htty (0.5.0) + protocol-http2 + protocol-rack (0.22.1) + io-stream (>= 0.10) + protocol-http (~> 0.58) + rack (>= 1.0) + protocol-url (0.11.0) + protocol-websocket (0.21.1) + protocol-http (~> 0.2) + rack (3.2.6) + samovar (2.5.1) + console (~> 1.0) + string-format (0.2.0) + xrb (0.11.2) + +PLATFORMS + arm64-darwin-24 + ruby + +DEPENDENCIES + io-watch + presently + +CHECKSUMS + agent-context (0.3.0) sha256=1bc73ffb4f73756417e349654b09953b42e8b26a4d091b4f5a13bd6c41d326fe + async (2.44.1) sha256=079bce0d019fb27cc58ec110fd9b9af4991d97b801d1c6cc6def1d1954dcf9b2 + async-actor (0.2.0) sha256=4d4c6397843a2e237386bd9536b9f0a437217daf83978e88e98f48a6deb9b12b + async-container (0.38.0) sha256=71479d8551870d684e602f463d7b8368909c835a64e38b555e1821512ffffc88 + async-http (0.99.0) sha256=665eba5e530b05a9682c78b3aa5a51e97dd67b8b25b71ba76530b60cf6c26a11 + async-http-cache (0.4.6) sha256=2038d1f093182f16b50b4db271c25085e3938da10bfcfc2904cadb0530fddfd6 + async-htty (0.5.0) sha256=c78172412ea592ef70c0c2a16a7a5055ae1ccd134db9b0e2df9dd93c73715e7a + async-pool (0.11.2) sha256=0a43a17b02b04d9c451b7d12fafa9a50e55dc6dd00d4369aca00433f16a7e3ed + async-service (0.25.0) sha256=71d9d75c2898dddf6549150cecd57156453e5b61da69fa87f10a0c97becfb6d1 + async-utilization (0.4.0) sha256=4da53cb1733a12c9cf70ff22bd37d29c10fa9162a03ddb10f34e12acd171fe32 + async-webdriver (0.12.2) sha256=49a82e6f60fd229547f5f298d58a43b1439925e37a981ed05743f7a4931d65c1 + async-websocket (0.30.1) sha256=54bb8a8f184e4aa64434c7a78ecc55850a67a3d1dcd02e3ae787376e2c673936 + bake (0.25.0) sha256=a47bdc6a26addc048827debc36fe27bb4d5d71ac2958ad910b5386e9baf49869 + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd + console (1.37.0) sha256=8093b286c2595a063849c098594fee5155ecc7967d36f3aa8cbe7569c8f3efd7 + falcon (0.57.0) sha256=c9b060bc23a3b90a80376078aae037d8f48e0b2f12f5f30f360d4b29d7baf0f7 + fiber-annotation (0.2.0) sha256=7abfadf1d119f508867d4103bf231c0354d019cc39a5738945dec2edadaf6c03 + fiber-local (1.1.0) sha256=c885f94f210fb9b05737de65d511136ea602e00c5105953748aa0f8793489f06 + fiber-storage (1.0.1) sha256=f48e5b6d8b0be96dac486332b55cee82240057065dc761c1ea692b2e719240e1 + io-endpoint (0.17.2) sha256=3feaf766c116b35839c11fac68b6aaadc47887bb488902a57bf8e1d288fb3338 + io-event (1.19.4) sha256=c291146e2e70849b9d6b64078f8e98f0154bfd3bc4f9759861ff13e9895e0cce + io-stream (0.14.0) sha256=07d9b13ea2066ccbbc761fff8d69ad5a31e47561d31e02cc271f3ed9c1a8f1cd + io-watch (0.6.3) sha256=95f80faf4b1ee00d8e184340830265ddae237b4f3a42b21f1e90593108932363 + json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a + live (0.18.2) sha256=e65253ba1f315fa4dc3ff31b4d2cfeb2ea76c29c13e70c83caf10276b018609d + lively (0.18.0) sha256=7871fc449b24e6699155648a1f3317b65dfe2771140d4f97f557444ecedb3681 + localhost (1.8.0) sha256=df7ea825b4f64949c588c17efac86bc47ddc4460d723778abe933b71759b2701 + markly (0.16.0) sha256=6f70d79e385b1efc9e171f74c81628826259039fe6c778e03c3924c71dac5511 + openssl (4.0.2) sha256=1037ad2868ae58df9ad917891c0c0f9815a1172f6846d4bcdd508e4c2ee747c2 + presently (0.16.0) sha256=2f10f0d1d2e3094b782a22e9abef2c6c6d9e3b10f21ab013eef3f5106344d8c4 + protocol-hpack (1.5.1) sha256=6feca238b8078da1cd295677d6f306c6001af92d75fe0643d33e6956cbc3ad91 + protocol-http (0.70.0) sha256=3d7e881be723c57191fcdf04ad238570cabca81142a1d64516f676eafcb913ad + protocol-http1 (0.40.2) sha256=add1191f06f16077b2e319ec5cf38c8623a2d2a9829f88a36ebaf8ecf80748a9 + protocol-http2 (0.26.2) sha256=1457cfaa31877512c349051e0094ea170b8913a62d966b82ff7cd14e4b1a3ede + protocol-htty (0.5.0) sha256=52ccd2d890d17d01346b96a99b942b48b8df82a1b9a861da87ba9d7eb8005e46 + protocol-rack (0.22.1) sha256=1185d245927ef9849a603700d6991ca353bc89724fbf98efa4a4333ed62a9fc3 + protocol-url (0.11.0) sha256=eeac547dc2ba370b26d17861fd1c77455170e24d7bd5cfebb1123395deab7d54 + protocol-websocket (0.21.1) sha256=34325e4325697f0956877e67784bcc838cfd51ebbf4f8e9e5201be292041ee61 + rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2 + samovar (2.5.1) sha256=8a9fc41eb8868084f0321eb41678c485cfbc7d282fb306c0be67c3284b1d2394 + string-format (0.2.0) sha256=bc981c14116b061f12134549f32fa2d61a17b5a35dd6fd36596c21722a789af6 + xrb (0.11.2) sha256=1e682acd8b7b58bbe3dc3dd0e5d3c1a6dbc7ff67d009abc50d94fac43b9f0870 + +BUNDLED WITH + 2.6.7 diff --git a/presentation/gems.rb b/presentation/gems.rb new file mode 100644 index 00000000..f5ae5c41 --- /dev/null +++ b/presentation/gems.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "presently" +gem "io-watch" diff --git a/presentation/public/_static/custom.css b/presentation/public/_static/custom.css new file mode 100644 index 00000000..0ae9f600 --- /dev/null +++ b/presentation/public/_static/custom.css @@ -0,0 +1,703 @@ +:root { + --latency-ns: #49a8ff; + --latency-us: #a875ff; + --latency-ms: #ffad4d; + --stream-a: var(--latency-ns); + --stream-b: var(--latency-us); + --stream-c: var(--latency-ms); +} + +.latency-slide, +.latency-analogy { + inset: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 4rem 5rem 3rem; +} + +.latency-heading h1 { + margin: 0; + font-size: 3.5rem; +} + +.latency-heading p { + margin: 0.4rem 0 0; + font-size: 1.25rem; + opacity: 0.65; +} + +.latency-items { + display: flex; + align-items: stretch; + justify-content: center; + gap: 2rem; + min-height: 65vh; +} + +.latency-item { + display: grid; + grid-template-rows: minmax(0, 1fr) 4.5rem 2.6rem; + align-items: end; + justify-items: center; + min-width: 7rem; + text-align: center; +} + +.latency-unit-ns { + --latency-color: var(--latency-ns); + --latency-color-dark: #2468ca; +} + +.latency-unit-us { + --latency-color: var(--latency-us); + --latency-color-dark: #6941c6; +} + +.latency-unit-ms { + --latency-color: var(--latency-ms); + --latency-color-dark: #d66b24; +} + +.diagram-latency { + align-self: end; + justify-self: center; + width: var(--latency-size); + height: var(--latency-size); + background: linear-gradient(135deg, var(--latency-color), var(--latency-color-dark)); + border: 1px solid rgba(255, 255, 255, 0.35); + border-radius: 0.45rem; + box-shadow: 0 0.8rem 2rem color-mix(in srgb, var(--latency-color) 25%, transparent); +} + +.latency-item p { + display: flex; + align-items: center; + justify-content: center; + max-width: 11rem; + margin: 0.4rem 0 0; + font-size: 1rem; + line-height: 1.2; +} + +.latency-item strong { + align-self: start; + color: var(--latency-color); + font-family: var(--font-mono); + font-size: 0.95rem; + line-height: 1.15; + max-width: 11rem; +} + +@media (max-width: 900px) { + .latency-slide, + .latency-analogy { + padding: 2rem; + } + + .latency-items { + gap: 1rem; + transform: scale(0.72); + transform-origin: bottom center; + } +} + +.latency-analogy { + text-align: center; +} + +.latency-analogy-flow { + display: grid; + grid-template-columns: minmax(16rem, 1fr) auto minmax(16rem, 1fr); + align-items: center; + gap: 3rem; +} + +.latency-analogy-card { + display: grid; + justify-items: center; + gap: 0.7rem; + padding: 2.5rem 2rem; + background: color-mix(in srgb, var(--latency-color) 12%, var(--surface)); + border: 2px solid var(--latency-color); + border-radius: 1.25rem; + box-shadow: 0 1rem 3rem color-mix(in srgb, var(--latency-color) 18%, transparent); +} + +.latency-analogy-card p, +.latency-analogy-card small { + margin: 0; +} + +.latency-analogy-card p { + font-size: 1.35rem; +} + +.latency-analogy-card strong { + color: var(--latency-color); + font-size: 3rem; +} + +.latency-analogy-card small { + font-size: 1rem; + opacity: 0.65; +} + +.latency-analogy-icon { + font-size: 3.5rem; +} + +.latency-analogy-multiplier { + display: grid; + gap: 0.25rem; + color: var(--slide-text); +} + +.latency-analogy-multiplier strong { + font-family: var(--font-mono); + font-size: 2rem; +} + +.latency-analogy-multiplier span { + opacity: 0.6; +} + +.latency-analogy-equation { + margin: 0; + font-family: var(--font-mono); + font-size: 1.2rem; + opacity: 0.55; +} + +@media (max-width: 900px) { + .latency-analogy-flow { + grid-template-columns: 1fr; + gap: 1rem; + } + + .latency-analogy-card { + padding: 1.25rem; + } +} + +.execution-slide { + inset: 0; + display: flex; + flex-direction: column; + padding: 3.5rem 5rem; +} + +.execution-visual { + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + min-height: 0; +} + +.execution-heading { + max-width: 70rem; +} + +.execution-heading h1 { + margin: 0; + font-size: clamp(2.2rem, 4vw, 3.8rem); + line-height: 1.05; + letter-spacing: -0.035em; +} + +.execution-kicker { + margin: 0 0 0.5rem; + color: var(--accent-light); + font-size: 1rem; + font-weight: 750; + letter-spacing: 0.15em; + text-transform: uppercase; +} + +.execution-time { + margin: 0 0 0.45rem 9rem; + font-family: var(--font-mono); + font-size: 0.9rem; + letter-spacing: 0.08em; + opacity: 0.5; + text-align: right; +} + +.execution-chart, +.async-sequence { + display: grid; + grid-template-columns: 9rem minmax(0, 1fr); + min-height: 17rem; +} + +.execution-labels, +.execution-column { + display: grid; + grid-template-rows: repeat(var(--execution-lanes), minmax(0, 1fr)); + gap: 0.55rem; +} + +.execution-labels span, +.async-sequence-labels span { + display: flex; + align-items: center; + justify-content: flex-end; + padding-right: 1rem; + font-family: var(--font-mono); + font-size: 0.95rem; + opacity: 0.72; +} + +.execution-columns { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 0.35rem; +} + +.async-sequence-labels { + display: grid; + grid-template-rows: 1fr 1fr; +} + +.async-sequence-track { + position: relative; + min-width: 0; +} + +.async-lifeline { + position: absolute; + left: 0; + right: 0; + height: 1px; + background: repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.22) 0 0.6rem, transparent 0.6rem 1rem); +} + +.async-lifeline-caller { + top: 25%; +} + +.async-lifeline-operation { + top: 75%; +} + +.async-initiation, +.async-operation-progress, +.async-caller-progress, +.async-completion, +.async-handling { + position: absolute; + inset: 0; +} + +.async-initiation { + --async-reveal-left: 1%; + --async-reveal-right: 73%; +} + +.async-operation-progress { + --async-reveal-left: 19%; + --async-reveal-right: 22%; +} + +.async-caller-progress { + --async-reveal-left: 24%; + --async-reveal-right: 24%; +} + +.async-completion { + --async-reveal-left: 77%; + --async-reveal-right: 12%; +} + +.async-handling { + --async-reveal-left: 81%; + --async-reveal-right: 1%; +} + +.async-block { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + height: 3.5rem; + border-radius: 0.55rem; + font-weight: 700; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 0.8rem 1.8rem rgba(0, 0, 0, 0.2); + transform: translateY(-50%); +} + +.async-block-start { + left: 1%; + top: 25%; + width: 18%; + background: var(--surface-light); +} + +.async-block-caller-work { + left: 24%; + top: 25%; + width: 52%; + background: var(--surface-light); +} + +.async-block-operation { + left: 19%; + top: 75%; + width: 59%; + background: var(--stream-a); + color: #07111f; +} + +.async-block-handle { + left: 81%; + top: 25%; + width: 18%; + background: var(--stream-b); + color: #120a20; +} + +.async-arrow { + position: absolute; + top: calc(25% + 1.75rem); + bottom: calc(25% + 1.75rem); + width: 2px; + background: var(--accent-light); +} + +.async-arrow span { + position: absolute; + left: 0.7rem; + top: 50%; + font-family: var(--font-mono); + font-size: 0.8rem; + transform: translateY(-50%); +} + +.async-arrow::after { + position: absolute; + left: 50%; + width: 0; + height: 0; + border-left: 0.4rem solid transparent; + border-right: 0.4rem solid transparent; + content: ""; + transform: translateX(-50%); +} + +.async-arrow-down { + left: 19%; +} + +.async-arrow-down::after { + bottom: -0.1rem; + border-top: 0.65rem solid var(--accent-light); +} + +.async-arrow-up { + left: 78%; +} + +.async-arrow-up::after { + top: -0.1rem; + border-bottom: 0.65rem solid var(--accent-light); +} + +.parallel-columns { + grid-template-columns: repeat(8, minmax(0, 1fr)); +} + +.execution-cell, +.execution-segment { + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + border-radius: 0.45rem; + font-size: 0.85rem; + font-weight: 700; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14); +} + +.stream-a { + background: var(--stream-a); + color: #07111f; +} + +.stream-b, +.request-ruby { + background: var(--stream-b); + color: #120a20; +} + +.stream-c, +.request-database { + background: var(--stream-c); + color: #211306; +} + +.concurrency-chart { + min-height: 7rem; +} + +.concurrency-timeline { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: 0.35rem; +} + +.execution-segment { + font-family: var(--font-mono); + font-size: 1.25rem; +} + +.execution-legend, +.request-legend { + display: flex; + justify-content: center; +} + +.execution-legend { + gap: 2rem; + margin-top: 1.2rem; +} + +.execution-legend span, +.request-legend span { + display: flex; + align-items: center; +} + +.execution-legend span { + gap: 0.5rem; + font-size: 0.95rem; + opacity: 0.8; +} + +.execution-legend i, +.request-legend i { + display: block; + width: 1rem; + height: 1rem; + border-radius: 0.25rem; +} + +.build-timeline { + animation: execution-fill 0.34s ease-out; + transform-origin: left center; +} + +.build-async-reveal { + animation: async-reveal 0.34s ease-out; +} + +@keyframes async-reveal { + from { + opacity: 0; + clip-path: inset(0 calc(100% - var(--async-reveal-left)) 0 var(--async-reveal-left)); + } + to { + opacity: 1; + clip-path: inset(0 var(--async-reveal-right) 0 var(--async-reveal-left)); + } +} + +@keyframes execution-fill { + from { + opacity: 0; + transform: scaleX(0); + } + to { + opacity: 1; + transform: scaleX(1); + } +} + +.request-latency-slide { + inset: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + padding: 3rem 4rem; +} + +.request-latency-heading { + text-align: center; +} + +.request-latency-heading h1 { + margin: 0; + font-size: clamp(2rem, 4vw, 3.5rem); +} + +.request-latency-visual { + display: flex; + flex-direction: column; + justify-content: center; + width: min(68rem, 92%); + margin: 0 auto; +} + +.request-track { + position: relative; + height: 7rem; + border: 2px solid rgba(255, 255, 255, 0.22); + border-radius: 0.7rem; + background: rgba(255, 255, 255, 0.04); + overflow: hidden; +} + +.request-bar { + display: grid; + height: 100%; +} + +.request-bar-original { + grid-template-columns: 10fr 90fr; +} + +.request-bar-optimized { + grid-template-columns: 1fr 90fr; + width: 91%; +} + +.request-segment { + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + font-family: var(--font-mono); + font-size: 1.25rem; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16); +} + +.request-saved { + position: absolute; + top: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + width: 9%; + height: 100%; + font-family: var(--font-mono); + font-size: 0.85rem; + font-weight: 700; + text-align: center; + opacity: 0.65; +} + +.request-total { + margin-top: 1rem; + font-family: var(--font-mono); + font-size: 1.4rem; + font-weight: 700; + text-align: right; +} + +.request-legend { + gap: 2.5rem; + margin-top: 2rem; +} + +.request-legend span { + gap: 0.6rem; +} + +.falcon-original-diagram { + width: min(64rem, 94%); + margin: 0 auto; +} + +.falcon-original-diagram .diagram-header { + padding: 0.8rem 1rem; + border-radius: 0.8rem 0.8rem 0 0; + background: var(--surface-light); + font-family: var(--font-mono); + font-weight: 700; + letter-spacing: 0.08em; + text-align: center; +} + +.falcon-original-diagram .diagram-grid { + display: grid; + gap: 1rem; + padding: 1rem; + border: 2px solid rgba(255, 255, 255, 0.18); + border-top: 0; + border-radius: 0 0 0.8rem 0.8rem; + background: rgba(255, 255, 255, 0.035); +} + +.falcon-original-diagram .diagram-box { + display: grid; + gap: 0.6rem; + padding: 1rem; + border: 1px solid rgba(255, 255, 255, 0.18); + border-radius: 0.75rem; + background: rgba(0, 0, 0, 0.14); +} + +.falcon-original-diagram .diagram-box-title { + font-family: var(--font-mono); + font-weight: 700; + opacity: 0.7; +} + +.falcon-original-diagram .falcon-worker-state { + align-content: start; + grid-template-rows: auto; + grid-auto-rows: 3.5rem; +} + +.falcon-original-diagram .component { + display: flex; + align-items: center; + justify-content: center; + padding: 0.7rem 1rem; + border: 1px solid rgba(255, 255, 255, 0.14); + border-radius: 0.5rem; + font-weight: 700; + text-align: center; +} + +.falcon-original-diagram .component.serving { + background: var(--stream-a); + color: #07111f; +} + +.falcon-original-diagram .component.blocked { + background: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.11) 0 0.55rem, rgba(255, 255, 255, 0.035) 0.55rem 1.1rem); + color: rgba(255, 255, 255, 0.65); +} + +.falcon-original-diagram .cpu-state { + padding: 0.7rem 1rem; + border-radius: 0.5rem; + background: var(--stream-c); + color: #211306; + font-family: var(--font-mono); + font-weight: 700; + text-align: center; +} + +@media (max-width: 900px) { + .execution-slide { + padding: 2rem; + } + + .execution-chart { + grid-template-columns: 6rem minmax(0, 1fr); + } + + .execution-time { + margin-left: 6rem; + } + + .execution-cell { + font-size: 0.65rem; + } + + .falcon-original-diagram .diagram-grid { + grid-template-columns: 1fr; + } +} diff --git a/presentation/readme.md b/presentation/readme.md new file mode 100644 index 00000000..b0494c05 --- /dev/null +++ b/presentation/readme.md @@ -0,0 +1,28 @@ +# Understanding Falcon + +Presentation for **Understanding Falcon with Samuel Williams — Online Store Lunch & Learn**. + +The deck explains why Falcon exists, how a request moves through the Async and +Protocol::HTTP stack, and how the runtime is deployed and operated in production. + +It contains 64 slides paced for 35 minutes, followed by discussion. Detailed +speaker notes are included with every slide. + + +## Setup + +```bash +$ bundle install +``` + +## Running + +```bash +$ bundle exec presently +``` + +Then open in your browser. + +Use the **arrow keys** (or swipe) to navigate between slides. + +For presenter notes, visit diff --git a/presentation/slides/010-welcome.md b/presentation/slides/010-welcome.md new file mode 100644 index 00000000..196e7624 --- /dev/null +++ b/presentation/slides/010-welcome.md @@ -0,0 +1,13 @@ +--- +template: title +duration: 20 +marker: Welcome +--- + +# Title + +Understanding Falcon + +--- + +Today we are going to dig learn about Falcon, and we are going to do that by starting at the observable output, and working backwards. \ No newline at end of file diff --git a/presentation/slides/020-scalability/010-scalability.md b/presentation/slides/020-scalability/010-scalability.md new file mode 100644 index 00000000..833695c7 --- /dev/null +++ b/presentation/slides/020-scalability/010-scalability.md @@ -0,0 +1,13 @@ +--- +template: section +duration: 20 +marker: Scalability +--- + +# Heading + +What is scalability? + +--- + +But before we get to that, the very first topic I want to touch on, underpins everything we are going to discuss. What is scalability? \ No newline at end of file diff --git a/presentation/slides/020-scalability/020-latency-ns.md b/presentation/slides/020-scalability/020-latency-ns.md new file mode 100644 index 00000000..3e4e0b19 --- /dev/null +++ b/presentation/slides/020-scalability/020-latency-ns.md @@ -0,0 +1,55 @@ +--- +template: diagram +duration: 20 +marker: Nanosecond Latency +--- + +
+
+

Latency in nanoseconds

+

Area is proportional to elapsed time.

+
+
+
+
+

L1 cache reference

+ 0.5 ns +
+
+
+

Branch mispredict

+ 5 ns +
+
+
+

L2 cache reference

+ 7 ns +
+
+
+

Mutex lock/unlock

+ 25 ns +
+
+
+

Main memory reference

+ 100 ns +
+
+
+ +--- + +Each square has an area proportional to the latency it represents. L1 is the unit square. + +```javascript +const latencies = slide.find(".latency-item").builder({effect: "scale"}) +latencies.show(0) + +slide + .after(250, () => latencies.next()) + .after(750, () => latencies.next()) + .after(750, () => latencies.next()) + .after(750, () => latencies.next()) + .after(750, () => latencies.next()) +``` diff --git a/presentation/slides/020-scalability/030-latency-us.md b/presentation/slides/020-scalability/030-latency-us.md new file mode 100644 index 00000000..a224cd14 --- /dev/null +++ b/presentation/slides/020-scalability/030-latency-us.md @@ -0,0 +1,55 @@ +--- +template: diagram +duration: 20 +marker: Microsecond Latency +--- + +
+
+

Latency in microseconds

+

Area is proportional to elapsed time.

+
+
+
+
+

Main memory reference

+ 100 ns = 0.1 µs +
+
+
+

Compress 1K bytes with Snappy

+ 3,000 ns = 3 µs +
+
+
+

Send 2K bytes over a 1 Gbps network

+ 20,000 ns = 20 µs +
+
+
+

SSD random read

+ 150,000 ns = 150 µs +
+
+
+

Read 1 MB sequentially from memory

+ 250,000 ns = 250 µs +
+
+
+ +--- + +The 100 nanosecond main-memory reference carries forward from the previous slide. Its area is one thirtieth of the three microsecond compression operation. + +```javascript +const latencies = slide.find(".latency-item").builder({effect: "scale"}) +latencies.show(0) + +slide + .after(250, () => latencies.next()) + .after(750, () => latencies.next()) + .after(750, () => latencies.next()) + .after(750, () => latencies.next()) + .after(750, () => latencies.next()) +``` diff --git a/presentation/slides/020-scalability/040-latency-ms.md b/presentation/slides/020-scalability/040-latency-ms.md new file mode 100644 index 00000000..1892212c --- /dev/null +++ b/presentation/slides/020-scalability/040-latency-ms.md @@ -0,0 +1,51 @@ +--- +template: diagram +duration: 24 +marker: Millisecond Latency +--- + +
+
+

Latency in milliseconds

+

Area is proportional to elapsed time.

+
+
+
+
+

Read 1 MB sequentially from memory

+ 250 µs = 0.25 ms +
+
+
+

Round trip within the same datacenter

+ 500,000 ns = 0.5 ms +
+
+
+

Read 10 MB sequentially from SSD

+ ≈ 5,000,000 ns = 5 ms +
+
+
+

Intercontinental network packet

+ 100,000,000 ns = 100 ms +
+
+
+ +--- + +The 250 microsecond memory read carries forward in violet. The new millisecond-scale operations are amber. + +The 10 MB SSD read assumes roughly 2 GB/s of effective sequential throughput: 10 MB ÷ 2,000 MB/s ≈ 5 ms. + +```javascript +const latencies = slide.find(".latency-item").builder({effect: "scale"}) +latencies.show(0) + +slide + .after(250, () => latencies.next()) + .after(750, () => latencies.next()) + .after(750, () => latencies.next()) + .after(750, () => latencies.next()) +``` diff --git a/presentation/slides/020-scalability/050-latency-coffee.md b/presentation/slides/020-scalability/050-latency-coffee.md new file mode 100644 index 00000000..44e60096 --- /dev/null +++ b/presentation/slides/020-scalability/050-latency-coffee.md @@ -0,0 +1,46 @@ +--- +template: diagram +duration: 20 +marker: Latency Analogy +--- + +
+
+

What does a hundred million times slower feel like?

+
+
+
+ +

If a CPU instruction took

+ 5 minutes + enough time to make a coffee +
+
+ 100,000,000× + longer +
+
+ 🌍 +

An intercontinental packet would take

+ 1,000 years + roughly a millennium +
+
+

100 ms ÷ 1 ns = 100,000,000

+
+ +--- + +For this analogy, round an instruction-scale interval to one nanosecond. Actual instruction throughput and latency vary by processor and instruction. + +Our 100 millisecond intercontinental packet is one hundred million times longer. If the instruction took five minutes—the time to make a coffee—the packet would take roughly 1,000 years. + +```javascript +const analogy = slide.find(".analogy-step").builder({effect: "scale"}) +analogy.show(0) + +slide + .after(300, () => analogy.next()) + .after(900, () => analogy.next()) + .after(900, () => analogy.next()) +``` diff --git a/presentation/slides/020-scalability/060-waiting-for-information.md b/presentation/slides/020-scalability/060-waiting-for-information.md new file mode 100644 index 00000000..2a3f539f --- /dev/null +++ b/presentation/slides/020-scalability/060-waiting-for-information.md @@ -0,0 +1,14 @@ +--- +template: statement +duration: 20 +marker: Waiting for Information +transition: fade +--- + +Processors can do a lot of work while waiting for information to become available. + +--- + +This is the central idea: computation is extraordinarily cheap relative to retrieving information from memory, storage, or a network. + +The question is not merely how fast the processor is. It is whether we can give it useful work while something else is waiting. diff --git a/presentation/slides/030-execution/010-execution.md b/presentation/slides/030-execution/010-execution.md new file mode 100644 index 00000000..01ad1ad1 --- /dev/null +++ b/presentation/slides/030-execution/010-execution.md @@ -0,0 +1,15 @@ +--- +template: section +duration: 15 +marker: Execution Models +--- + +# Heading + +Asynchrony & Execution Strategies + +--- + +Asynchrony is the root capability: work can remain outstanding without blocking progress. + +Concurrency and parallelism are strategies for executing that outstanding work. They are not mutually exclusive. diff --git a/presentation/slides/030-execution/020-asynchrony.md b/presentation/slides/030-execution/020-asynchrony.md new file mode 100644 index 00000000..4ca0f4e8 --- /dev/null +++ b/presentation/slides/030-execution/020-asynchrony.md @@ -0,0 +1,19 @@ +--- +template: title +duration: 20 +marker: Asynchrony +--- + +# Title + +Asynchrony + +# Subtitle + +Work can remain outstanding without blocking progress. + +--- + +Asynchrony is the root capability: initiation and completion are decoupled, so an operation can remain outstanding while the initiator continues making progress. + +It does not prescribe how that outstanding work executes. It may be interleaved concurrently, executed in parallel, or handled by an external system. diff --git a/presentation/slides/030-execution/025-asynchrony-diagram.md b/presentation/slides/030-execution/025-asynchrony-diagram.md new file mode 100644 index 00000000..61451af4 --- /dev/null +++ b/presentation/slides/030-execution/025-asynchrony-diagram.md @@ -0,0 +1,51 @@ +--- +template: diagram +duration: 25 +transition: fade +--- + +
+
+

Asynchrony over time

+

Initiate now. Complete later.

+
+
+
time →
+
+
+ Caller + Operation +
+
+
+
+
+
Start operation
+
initiate
+
+
+
Operation in progress
+
+
+
Continue useful work
+
+
+
complete
+
+
+
Handle result
+
+
+
+
+
+ +--- + +The caller is free between initiation and completion. + +```javascript +const timeline = slide.find(".execution-step").builder({effect: "async-reveal"}) +timeline.show(0) +timeline.play(650) +``` diff --git a/presentation/slides/030-execution/030-parallelism.md b/presentation/slides/030-execution/030-parallelism.md new file mode 100644 index 00000000..e1291932 --- /dev/null +++ b/presentation/slides/030-execution/030-parallelism.md @@ -0,0 +1,17 @@ +--- +template: title +duration: 20 +marker: Parallelism +--- + +# Title + +Parallelism + +# Subtitle + +Multiple operations execute simultaneously using multiple execution resources. + +--- + +Parallelism requires multiple execution resources. It can reduce the latency of a divisible job, increase total throughput, or both—but coordination and uneven work impose limits. diff --git a/presentation/slides/030-execution/035-parallelism-diagram.md b/presentation/slides/030-execution/035-parallelism-diagram.md new file mode 100644 index 00000000..80ebfde8 --- /dev/null +++ b/presentation/slides/030-execution/035-parallelism-diagram.md @@ -0,0 +1,47 @@ +--- +template: diagram +duration: 25 +transition: fade +--- + +
+
+

Parallel execution

+

Three tasks. Three cores.

+
+
+
time →
+
+
+ Core 1 + Core 2 + Core 3 +
+
+
+
+
+
+
+
+
+
+
+
+
+ Task A + Task B + Task C +
+
+
+ +--- + +All three tasks execute at the same instant on separate CPU cores. + +```javascript +const timeline = slide.find(".execution-step").builder({effect: "timeline"}) +timeline.show(0) +timeline.play(300) +``` diff --git a/presentation/slides/030-execution/040-concurrency.md b/presentation/slides/030-execution/040-concurrency.md new file mode 100644 index 00000000..480a5787 --- /dev/null +++ b/presentation/slides/030-execution/040-concurrency.md @@ -0,0 +1,19 @@ +--- +template: title +duration: 20 +marker: Concurrency +--- + +# Title + +Concurrency + +# Subtitle + +Multiple operations make progress by interleaving on shared execution resources. + +--- + +Concurrency is about structuring multiple tasks that are in progress together. They need not execute at the same instant: one processor can interleave them whenever a task waits or yields. + +Hyper-threading is not a pure single-resource example. It is simultaneous multithreading: two hardware threads share one core's execution resources and may execute instructions simultaneously. It combines resource sharing with limited parallelism. diff --git a/presentation/slides/030-execution/045-concurrency-diagram.md b/presentation/slides/030-execution/045-concurrency-diagram.md new file mode 100644 index 00000000..60d7b573 --- /dev/null +++ b/presentation/slides/030-execution/045-concurrency-diagram.md @@ -0,0 +1,49 @@ +--- +template: diagram +duration: 25 +transition: fade +--- + +
+
+

Concurrent execution

+

Three tasks. One processor.

+
+
+
time →
+
+
+ Processor +
+
+
A
+
A
+
B
+
B
+
C
+
A
+
C
+
B
+
A
+
C
+
C
+
B
+
+
+
+ Task A + Task B + Task C +
+
+
+ +--- + +The processor interleaves three tasks, switching whenever a task waits or yields. + +```javascript +const timeline = slide.find(".execution-step").builder({effect: "timeline"}) +timeline.show(0) +timeline.play(260) +``` diff --git a/presentation/slides/030-execution/050-execution-tradeoffs.md b/presentation/slides/030-execution/050-execution-tradeoffs.md new file mode 100644 index 00000000..ccb0dd3e --- /dev/null +++ b/presentation/slides/030-execution/050-execution-tradeoffs.md @@ -0,0 +1,14 @@ +--- +template: statement +duration: 20 +marker: Execution Trade-offs +transition: fade +--- + +Concurrency and parallelism let us balance latency, throughput, and hardware efficiency. + +--- + +These are execution strategies, not universal improvements. + +Concurrency can improve utilization by doing useful work while other tasks wait. Parallelism can reduce latency or increase throughput by applying more execution resources. Both introduce coordination costs and trade-offs. diff --git a/presentation/slides/030-execution/060-asynchronous-coordination.md b/presentation/slides/030-execution/060-asynchronous-coordination.md new file mode 100644 index 00000000..d6ce31fe --- /dev/null +++ b/presentation/slides/030-execution/060-asynchronous-coordination.md @@ -0,0 +1,14 @@ +--- +template: statement +duration: 20 +marker: Asynchronous Coordination +transition: fade +--- + +Asynchronous execution can make programs faster—when it saves more time than it costs. + +--- + +Asynchronous execution is not free. Scheduling, synchronization, buffering, and state management all add overhead. + +The trade-off is worthwhile when coordinating outstanding work eliminates more idle time than the coordination itself consumes. diff --git a/presentation/slides/040-waiting/010-request-latency.md b/presentation/slides/040-waiting/010-request-latency.md new file mode 100644 index 00000000..9d3b4072 --- /dev/null +++ b/presentation/slides/040-waiting/010-request-latency.md @@ -0,0 +1,35 @@ +--- +template: diagram +duration: 20 +transition: fade +--- + +
+
+

Request latency

+

10 ms Ruby processing + 90 ms database wait

+
+
+
+
+
10 ms
+
90 ms
+
+
+
100 ms total
+
+ Ruby processing + Database wait +
+
+
+ +--- + +Only ten percent of the request is spent executing Ruby. The remaining ninety percent is spent waiting for the database. + +```javascript +const timeline = slide.find(".execution-step").builder({effect: "timeline"}) +timeline.show(0) +timeline.play(650) +``` diff --git a/presentation/slides/040-waiting/020-ruby-faster.md b/presentation/slides/040-waiting/020-ruby-faster.md new file mode 100644 index 00000000..d5ce6257 --- /dev/null +++ b/presentation/slides/040-waiting/020-ruby-faster.md @@ -0,0 +1,12 @@ +--- +template: statement +duration: 15 +marker: Optimization +transition: fade +--- + +What if we made Ruby **10× faster?** + +--- + +Suppose we optimize the Ruby processing from ten milliseconds down to one millisecond, without changing the database latency. diff --git a/presentation/slides/040-waiting/030-request-latency-optimized.md b/presentation/slides/040-waiting/030-request-latency-optimized.md new file mode 100644 index 00000000..8efef6da --- /dev/null +++ b/presentation/slides/040-waiting/030-request-latency-optimized.md @@ -0,0 +1,36 @@ +--- +template: diagram +duration: 20 +transition: fade +--- + +
+
+

After optimization

+

1 ms Ruby processing + 90 ms database wait

+
+
+
+
+
+
90 ms
+
+
9 ms saved
+
+
91 ms total
+
+ Ruby processing: 1 ms + Database wait: 90 ms +
+
+
+ +--- + +Ruby is now ten times faster, but the database wait is unchanged. Total request latency falls from one hundred milliseconds to ninety-one milliseconds. + +```javascript +const timeline = slide.find(".execution-step").builder({effect: "timeline"}) +timeline.show(0) +timeline.play(650) +``` diff --git a/presentation/slides/040-waiting/040-waiting-dominates.md b/presentation/slides/040-waiting/040-waiting-dominates.md new file mode 100644 index 00000000..b2bfe1e0 --- /dev/null +++ b/presentation/slides/040-waiting/040-waiting-dominates.md @@ -0,0 +1,14 @@ +--- +template: statement +duration: 20 +marker: Throughput +transition: fade +--- + +So… how do we handle more requests? + +--- + +Making Ruby ten times faster only reduced total latency from 100 milliseconds to 91 milliseconds—roughly a 1.1× speedup. + +If most of each request is spent waiting, how can we use that waiting time to increase throughput? diff --git a/presentation/slides/050-falcon/010-falcon.md b/presentation/slides/050-falcon/010-falcon.md new file mode 100644 index 00000000..5c286edc --- /dev/null +++ b/presentation/slides/050-falcon/010-falcon.md @@ -0,0 +1,14 @@ +--- +template: section +duration: 8 +marker: Falcon +transition: fade +--- + +# Heading + +Falcon + +--- + +Falcon is a web server designed to keep making progress while requests wait for I/O. diff --git a/presentation/slides/050-falcon/020-falcon-server.md b/presentation/slides/050-falcon/020-falcon-server.md new file mode 100644 index 00000000..6bfe0aa6 --- /dev/null +++ b/presentation/slides/050-falcon/020-falcon-server.md @@ -0,0 +1,12 @@ +--- +template: statement +duration: 15 +marker: Falcon +transition: fade +--- + +Falcon is a fiber-based, asynchronous Ruby web server. + +--- + +Falcon runs Rack applications on top of an asynchronous HTTP stack. Its workers use Async to handle multiple connections and requests concurrently. diff --git a/presentation/slides/050-falcon/030-falcon-idea.md b/presentation/slides/050-falcon/030-falcon-idea.md new file mode 100644 index 00000000..12b7f4df --- /dev/null +++ b/presentation/slides/050-falcon/030-falcon-idea.md @@ -0,0 +1,12 @@ +--- +template: statement +duration: 15 +marker: The Idea +transition: fade +--- + +When one request waits on I/O, let the worker run another request. + +--- + +Database queries and network calls can take far longer than Ruby processing. Falcon uses that waiting time to make progress on other requests. diff --git a/presentation/slides/050-falcon/040-falcon-concurrency.md b/presentation/slides/050-falcon/040-falcon-concurrency.md new file mode 100644 index 00000000..d7bf45f8 --- /dev/null +++ b/presentation/slides/050-falcon/040-falcon-concurrency.md @@ -0,0 +1,54 @@ +--- +template: diagram +duration: 20 +transition: fade +--- + +
+
+

One Falcon worker

+

When one request waits, another can run.

+
+
+
+
Falcon
+
+
+
Worker 1
+
Request A · I/O Wait
+
Request B · Running
+
Request C · I/O Wait
+
+
+
Worker 2
+
Request D · I/O Wait
+
Request E · Running
+
+
CPU Busy
+
CPU Busy
+
+
+
+
+ +--- + +Each Falcon worker can keep multiple requests in flight. When one fiber waits for I/O, another fiber can use the worker. + +```javascript +const boxes = slide.find(".diagram-box").builder({effect: "fade"}) +const components = slide.find(".component").builder({effect: "fly-up"}) +const cpuStates = slide.find(".cpu-state").builder({effect: "fly-up"}) +boxes.show(0) +components.show(0) +cpuStates.show(0) +slide + .after(300, () => boxes.next()) + .after(300, () => components.next()) + .after(250, () => components.next()) + .after(250, () => components.next()) + .after(400, () => boxes.next()) + .after(300, () => components.next()) + .after(250, () => components.next()) + .after(500, () => cpuStates.play(200)) +``` diff --git a/presentation/slides/050-falcon/060-falcon-execution.md b/presentation/slides/050-falcon/060-falcon-execution.md new file mode 100644 index 00000000..20458526 --- /dev/null +++ b/presentation/slides/050-falcon/060-falcon-execution.md @@ -0,0 +1,14 @@ +--- +template: statement +duration: 20 +marker: Falcon +transition: fade +--- + +Workers provide **parallelism.** Fibers provide **concurrency.** + +--- + +Falcon combines both execution strategies: multiple workers can execute simultaneously, while each worker interleaves many requests efficiently. + +The next question is how Async makes that interleaving possible without turning application code into callbacks. diff --git a/presentation/slides/060-async/010-async.md b/presentation/slides/060-async/010-async.md new file mode 100644 index 00000000..cd5ea500 --- /dev/null +++ b/presentation/slides/060-async/010-async.md @@ -0,0 +1,14 @@ +--- +template: section +duration: 8 +marker: Async +transition: fade +--- + +# Heading + +Async + +--- + +Async provides the task, fiber scheduler, and event loop that Falcon uses for concurrency. diff --git a/presentation/slides/060-async/020-io-wait.md b/presentation/slides/060-async/020-io-wait.md new file mode 100644 index 00000000..be56f2de --- /dev/null +++ b/presentation/slides/060-async/020-io-wait.md @@ -0,0 +1,12 @@ +--- +template: statement +duration: 18 +marker: Async +transition: fade +--- + +Async turns I/O waits into suspension points. + +--- + +Each task runs inside a fiber. When an operation would block on I/O, the fiber is suspended and the scheduler can run another ready task on the same worker. diff --git a/presentation/slides/060-async/030-concurrent-requests.md b/presentation/slides/060-async/030-concurrent-requests.md new file mode 100644 index 00000000..07c47e21 --- /dev/null +++ b/presentation/slides/060-async/030-concurrent-requests.md @@ -0,0 +1,26 @@ +--- +template: code +duration: 12 +title: Two Concurrent Requests +--- + +```ruby +Async do |server| + request_a = server.async do + cart = Core.fetch_cart(session_a) + send_response(200, render(cart)) + end + + request_b = server.async do + products = Spanner.query_catalog(shop) + send_response(200, render(products)) + end + + request_a.wait + request_b.wait +end +``` + +--- + +Each request is represented by an Async task. Within each task, the code remains sequential. diff --git a/presentation/slides/060-async/040-request-a.md b/presentation/slides/060-async/040-request-a.md new file mode 100644 index 00000000..8518029c --- /dev/null +++ b/presentation/slides/060-async/040-request-a.md @@ -0,0 +1,27 @@ +--- +template: code +duration: 12 +focus: 2-5 +title: Request A Runs Until It Waits +--- + +```ruby +Async do |server| + request_a = server.async do + cart = Core.fetch_cart(session_a) + send_response(200, render(cart)) + end + + request_b = server.async do + products = Spanner.query_catalog(shop) + send_response(200, render(products)) + end + + request_a.wait + request_b.wait +end +``` + +--- + +Request A runs normally until the cart fetch must wait for the network. Its fiber is suspended, leaving the worker free to run another task. diff --git a/presentation/slides/060-async/050-request-b.md b/presentation/slides/060-async/050-request-b.md new file mode 100644 index 00000000..7c3f4a69 --- /dev/null +++ b/presentation/slides/060-async/050-request-b.md @@ -0,0 +1,27 @@ +--- +template: code +duration: 12 +focus: 7-10 +title: Request B Runs While A Waits +--- + +```ruby +Async do |server| + request_a = server.async do + cart = Core.fetch_cart(session_a) + send_response(200, render(cart)) + end + + request_b = server.async do + products = Spanner.query_catalog(shop) + send_response(200, render(products)) + end + + request_a.wait + request_b.wait +end +``` + +--- + +The scheduler transfers control to Request B. Both requests remain in progress, but only one fiber executes at a time on this worker. diff --git a/presentation/slides/060-async/060-wait-for-results.md b/presentation/slides/060-async/060-wait-for-results.md new file mode 100644 index 00000000..e43b2830 --- /dev/null +++ b/presentation/slides/060-async/060-wait-for-results.md @@ -0,0 +1,27 @@ +--- +template: code +duration: 12 +focus: 12-13 +title: Wait for Both Requests +--- + +```ruby +Async do |server| + request_a = server.async do + cart = Core.fetch_cart(session_a) + send_response(200, render(cart)) + end + + request_b = server.async do + products = Spanner.query_catalog(shop) + send_response(200, render(products)) + end + + request_a.wait + request_b.wait +end +``` + +--- + +The parent task waits for both child tasks to finish. While either request is waiting on I/O, other ready tasks can continue to execute. diff --git a/presentation/slides/060-async/070-io-event.md b/presentation/slides/060-async/070-io-event.md new file mode 100644 index 00000000..993f16a6 --- /dev/null +++ b/presentation/slides/060-async/070-io-event.md @@ -0,0 +1,12 @@ +--- +template: statement +duration: 15 +marker: Event Loop +transition: fade +--- + +`io-event` waits for readiness. Async decides what runs next. + +--- + +The `io-event` selector integrates with the operating system to wait efficiently for I/O events. The Async scheduler resumes the fibers whose operations are ready to continue. diff --git a/presentation/slides/060-async/080-scheduler.md b/presentation/slides/060-async/080-scheduler.md new file mode 100644 index 00000000..2675a180 --- /dev/null +++ b/presentation/slides/060-async/080-scheduler.md @@ -0,0 +1,27 @@ +--- +template: code +duration: 10 +title: A Simplified Fiber Scheduler +transition: fade +--- + +```ruby +class Scheduler + def io_wait(io, events, timeout) + fiber = Fiber.current + selector.io_wait(fiber, io, events) + + transfer + end + + def run + while fiber = selector.select + fiber.transfer + end + end +end +``` + +--- + +Conceptually, the scheduler has two responsibilities: suspend fibers that must wait, and resume fibers that are ready. diff --git a/presentation/slides/060-async/090-scheduler-wait.md b/presentation/slides/060-async/090-scheduler-wait.md new file mode 100644 index 00000000..cb736666 --- /dev/null +++ b/presentation/slides/060-async/090-scheduler-wait.md @@ -0,0 +1,27 @@ +--- +template: code +duration: 12 +focus: 2-7 +title: Suspend the Waiting Fiber +--- + +```ruby +class Scheduler + def io_wait(io, events, timeout) + fiber = Fiber.current + selector.io_wait(fiber, io, events) + + transfer + end + + def run + while fiber = selector.select + fiber.transfer + end + end +end +``` + +--- + +The scheduler registers the current fiber's interest in the I/O event, then transfers control back to the event loop. diff --git a/presentation/slides/060-async/100-event-loop.md b/presentation/slides/060-async/100-event-loop.md new file mode 100644 index 00000000..62777e52 --- /dev/null +++ b/presentation/slides/060-async/100-event-loop.md @@ -0,0 +1,27 @@ +--- +template: code +duration: 12 +focus: 9-12 +title: Resume Ready Fibers +--- + +```ruby +class Scheduler + def io_wait(io, events, timeout) + fiber = Fiber.current + selector.io_wait(fiber, io, events) + + transfer + end + + def run + while fiber = selector.select + fiber.transfer + end + end +end +``` + +--- + +The event loop waits for readiness and transfers control to fibers that can make progress. When they need to wait again, control returns to the loop. diff --git a/presentation/slides/060-async/110-async-result.md b/presentation/slides/060-async/110-async-result.md new file mode 100644 index 00000000..ffb29a85 --- /dev/null +++ b/presentation/slides/060-async/110-async-result.md @@ -0,0 +1,12 @@ +--- +template: statement +duration: 20 +marker: Async +transition: fade +--- + +Concurrency is transparent to the application code—no changes to existing application logic required. + +--- + +Falcon and Ruby's fiber scheduler coordinate I/O waits beneath the Rack application. Existing request-handling logic can retain its ordinary sequential control flow.