Packages
snakepit
0.9.1
0.13.0
0.12.0
0.11.1
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
High-performance pooler and session manager for external language integrations. Supports Python, Node.js, Ruby, and more with gRPC streaming, session management, and production-ready process cleanup.
Current section
53 Versions
Jump to
Current section
53 Versions
Compare versions
35
files changed
+874
additions
-4252
deletions
| @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |
| 7 7 | |
| 8 8 | ## [Unreleased] |
| 9 9 | |
| 10 | + ## [0.9.1] - 2026-01-09 |
| 11 | + |
| 12 | + ### Added |
| 13 | + - `ClientSupervisor` wrapper for safe gRPC client supervision across gRPC variants. |
| 14 | + - gRPC server request logging interceptor with optional `:grpc_request_logging` and category-aware debug output. |
| 15 | + - `mix snakepit.python_test` task to bootstrap and run the Python test suite (supports `--no-bootstrap`). |
| 16 | + - Pool reconciliation loop to restore minimum worker counts after crash storms (configurable via `pool_reconcile_interval_ms` and `pool_reconcile_batch_size`). |
| 17 | + - Configurable restart intensity for worker starters and worker supervisors (`worker_starter_*` and `worker_supervisor_*` defaults). |
| 18 | + |
| 19 | + ### Changed |
| 20 | + - gRPC client and worker stream defaults now derive from `grpc_command_timeout/0` and `stream_timeout/0`. |
| 21 | + - Pool and worker execution now handle `:infinity` timeouts without deadline bookkeeping. |
| 22 | + - Python gRPC server now runs sync adapter calls in worker threads by default; use `thread_sensitive` metadata or `SNAKEPIT_THREAD_SENSITIVE` to keep execution on the main thread. |
| 23 | + - `Snakepit.Pool` metadata validation now accepts `Snakepit.Pool` as the default pool identifier. |
| 24 | + - gRPC is pinned to `0.11.5` and protobuf is pinned to `0.16.0` (override). |
| 25 | + |
| 26 | + ### Fixed |
| 27 | + - gRPC status code 4 now maps to `{:error, :timeout}` in the client. |
| 28 | + - Process group shutdown waits for group exit using `/proc` or `ps`, avoiding zombie false positives. |
| 29 | + - Test suite now tracks and terminates leaked external Python processes after runs. |
| 30 | + |
| 10 31 | ## [0.9.0] - 2026-01-02 |
| 11 32 | |
| 12 33 | ### Added |
| @@ -1505,7 +1526,9 @@ This release also rolls up the previously undocumented fail-fast docs/tests work | |
| 1505 1526 | - Configurable pool sizes and timeouts |
| 1506 1527 | - Built-in bridge scripts for Python and JavaScript |
| 1507 1528 | |
| 1508 | - [Unreleased]: https://github.com/nshkrdotcom/snakepit/compare/v0.8.5...HEAD |
| 1529 | + [Unreleased]: https://github.com/nshkrdotcom/snakepit/compare/v0.9.1...HEAD |
| 1530 | + [0.9.1]: https://github.com/nshkrdotcom/snakepit/releases/tag/v0.9.1 |
| 1531 | + [0.9.0]: https://github.com/nshkrdotcom/snakepit/releases/tag/v0.9.0 |
| 1509 1532 | [0.8.5]: https://github.com/nshkrdotcom/snakepit/releases/tag/v0.8.5 |
| 1510 1533 | [0.8.4]: https://github.com/nshkrdotcom/snakepit/releases/tag/v0.8.4 |
| 1511 1534 | [0.8.3]: https://github.com/nshkrdotcom/snakepit/releases/tag/v0.8.3 |
| @@ -31,7 +31,7 @@ Add `snakepit` to your dependencies in `mix.exs`: | |
| 31 31 | ```elixir |
| 32 32 | def deps do |
| 33 33 | [ |
| 34 | - {:snakepit, "~> 0.9.0"} |
| 34 | + {:snakepit, "~> 0.9.1"} |
| 35 35 | ] |
| 36 36 | end |
| 37 37 | ``` |
| @@ -50,7 +50,7 @@ For higher-level Python integration with compile-time type generation, use [Snak | |
| 50 50 | |
| 51 51 | ```elixir |
| 52 52 | def deps do |
| 53 | - [{:snakebridge, "~> 0.8.0"}] |
| 53 | + [{:snakebridge, "~> 0.9.1"}] |
| 54 54 | end |
| 55 55 | |
| 56 56 | def project do |
| @@ -126,7 +126,7 @@ config :snakepit, log_level: :none # Complete silence | |
| 126 126 | config :snakepit, log_level: :debug, log_categories: [:grpc, :pool] |
| 127 127 | ``` |
| 128 128 | |
| 129 | - ### Runtime Configurable Defaults (v0.8.8+) |
| 129 | + ### Runtime Configurable Defaults |
| 130 130 | |
| 131 131 | All hardcoded timeout and sizing values are now configurable via `Application.get_env/3`. |
| 132 132 | Values are read at runtime, allowing configuration changes without recompilation. |
| @@ -151,6 +151,16 @@ config :snakepit, | |
| 151 151 | pool_startup_batch_size: 10, # Workers started per batch |
| 152 152 | pool_startup_batch_delay_ms: 500, # Delay between startup batches |
| 153 153 | |
| 154 | + # Pool recovery |
| 155 | + pool_reconcile_interval_ms: 1_000, # Reconcile worker count interval (0 disables) |
| 156 | + pool_reconcile_batch_size: 2, # Max workers respawned per tick |
| 157 | + |
| 158 | + # Worker supervisor restart intensity |
| 159 | + worker_starter_max_restarts: 3, |
| 160 | + worker_starter_max_seconds: 5, |
| 161 | + worker_supervisor_max_restarts: 3, |
| 162 | + worker_supervisor_max_seconds: 5, |
| 163 | + |
| 154 164 | # Retry policy |
| 155 165 | retry_max_attempts: 3, |
| 156 166 | retry_backoff_sequence: [100, 200, 400, 800, 1600], |
| @@ -1,55 +0,0 @@ | |
| 1 | - <svg width="48" height="48" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> |
| 2 | - <defs> |
| 3 | - <linearGradient id="pitBg" x1="0%" y1="0%" x2="100%" y2="100%"> |
| 4 | - <stop offset="0%" style="stop-color:#1c2331; stop-opacity:1" /> |
| 5 | - <stop offset="100%" style="stop-color:#0d1117; stop-opacity:1" /> |
| 6 | - </linearGradient> |
| 7 | - <linearGradient id="snake1" x1="0%" y1="0%" x2="100%" y2="100%"> |
| 8 | - <stop offset="0%" style="stop-color:#306998; stop-opacity:1" /> |
| 9 | - <stop offset="100%" style="stop-color:#4b8bbe; stop-opacity:1" /> |
| 10 | - </linearGradient> |
| 11 | - <linearGradient id="snake2" x1="0%" y1="0%" x2="100%" y2="100%"> |
| 12 | - <stop offset="0%" style="stop-color:#ffd43b; stop-opacity:1" /> |
| 13 | - <stop offset="100%" style="stop-color:#ffe873; stop-opacity:1" /> |
| 14 | - </linearGradient> |
| 15 | - <filter id="glow"> |
| 16 | - <feGaussianBlur stdDeviation="1" result="coloredBlur"/> |
| 17 | - <feMerge> |
| 18 | - <feMergeNode in="coloredBlur"/> |
| 19 | - <feMergeNode in="SourceGraphic"/> |
| 20 | - </feMerge> |
| 21 | - </filter> |
| 22 | - </defs> |
| 23 | - |
| 24 | - <!-- Hexagonal Pit --> |
| 25 | - <polygon points="50,8 90,28 90,72 50,92 10,72 10,28" |
| 26 | - fill="url(#pitBg)" stroke="#2a3441" stroke-width="2"/> |
| 27 | - |
| 28 | - <!-- Blue Snake (left/top) --> |
| 29 | - <g> |
| 30 | - <path d="M 30,75 Q 25,60 30,45 Q 35,30 45,25 Q 55,20 60,30 Q 62,35 58,40" |
| 31 | - fill="none" stroke="url(#snake1)" stroke-width="7" stroke-linecap="round"/> |
| 32 | - <path d="M 30,45 L 24,42 L 28,38 C 32,36 34,38 32,42 Z" |
| 33 | - fill="url(#snake1)"/> |
| 34 | - <circle cx="28" cy="40" r="1.5" fill="#ffd43b" filter="url(#glow)"/> |
| 35 | - </g> |
| 36 | - |
| 37 | - <!-- Yellow Snake (right/bottom) --> |
| 38 | - <g> |
| 39 | - <path d="M 70,25 Q 75,40 70,55 Q 65,70 55,75 Q 45,80 40,70 Q 38,65 42,60" |
| 40 | - fill="none" stroke="url(#snake2)" stroke-width="7" stroke-linecap="round"/> |
| 41 | - <path d="M 70,55 L 76,58 L 72,62 C 68,64 66,62 68,58 Z" |
| 42 | - fill="url(#snake2)"/> |
| 43 | - <circle cx="72" cy="60" r="1.5" fill="#306998" filter="url(#glow)"/> |
| 44 | - </g> |
| 45 | - |
| 46 | - <!-- Intertwined center section --> |
| 47 | - <path d="M 58,40 Q 50,45 42,60" |
| 48 | - fill="none" stroke="url(#snake1)" stroke-width="6" stroke-linecap="round" opacity="0.9"/> |
| 49 | - <path d="M 42,60 Q 50,55 58,40" |
| 50 | - fill="none" stroke="url(#snake2)" stroke-width="6" stroke-linecap="round" opacity="0.9"/> |
| 51 | - |
| 52 | - <!-- Pit edge highlights --> |
| 53 | - <polygon points="50,8 90,28 90,72 50,92 10,72 10,28" |
| 54 | - fill="none" stroke="#4a5568" stroke-width="1" opacity="0.3"/> |
| 55 | - </svg> |
| @@ -1,387 +0,0 @@ | |
| 1 | - <svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"> |
| 2 | - <defs> |
| 3 | - <!-- Python Blue Gradient --> |
| 4 | - <linearGradient id="pythonBlue" x1="0%" y1="0%" x2="100%" y2="100%"> |
| 5 | - <stop offset="0%" stop-color="#306998"/> |
| 6 | - <stop offset="50%" stop-color="#4b8bbe"/> |
| 7 | - <stop offset="100%" stop-color="#5a9fd4"/> |
| 8 | - </linearGradient> |
| 9 | - |
| 10 | - <!-- Python Yellow Gradient --> |
| 11 | - <linearGradient id="pythonYellow" x1="0%" y1="0%" x2="100%" y2="100%"> |
| 12 | - <stop offset="0%" stop-color="#ffd43b"/> |
| 13 | - <stop offset="50%" stop-color="#ffe873"/> |
| 14 | - <stop offset="100%" stop-color="#fff3a3"/> |
| 15 | - </linearGradient> |
| 16 | - |
| 17 | - <!-- Dark Pit Gradient (radial for depth) --> |
| 18 | - <radialGradient id="pitDepth" cx="50%" cy="50%" r="60%"> |
| 19 | - <stop offset="0%" stop-color="#0a0f1a"/> |
| 20 | - <stop offset="40%" stop-color="#0d121f"/> |
| 21 | - <stop offset="70%" stop-color="#141b2d"/> |
| 22 | - <stop offset="100%" stop-color="#1c2331"/> |
| 23 | - </radialGradient> |
| 24 | - |
| 25 | - <!-- Hexagon Border Gradient --> |
| 26 | - <linearGradient id="hexBorder" x1="0%" y1="0%" x2="100%" y2="100%"> |
| 27 | - <stop offset="0%" stop-color="#4b8bbe"/> |
| 28 | - <stop offset="50%" stop-color="#ffd43b"/> |
| 29 | - <stop offset="100%" stop-color="#4b8bbe"/> |
| 30 | - </linearGradient> |
| 31 | - |
| 32 | - <!-- Snake Scale Pattern --> |
| 33 | - <pattern id="snakeScales" patternUnits="userSpaceOnUse" width="6" height="5"> |
| 34 | - <ellipse cx="3" cy="2.5" rx="2.8" ry="2.2" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="0.4"/> |
| 35 | - </pattern> |
| 36 | - |
| 37 | - <!-- Eye Glow Filter --> |
| 38 | - <filter id="eyeGlow" x="-50%" y="-50%" width="200%" height="200%"> |
| 39 | - <feGaussianBlur stdDeviation="1.5" result="glow"/> |
| 40 | - <feMerge> |
| 41 | - <feMergeNode in="glow"/> |
| 42 | - <feMergeNode in="glow"/> |
| 43 | - <feMergeNode in="SourceGraphic"/> |
| 44 | - </feMerge> |
| 45 | - </filter> |
| 46 | - |
| 47 | - <!-- Snake Body Glow --> |
| 48 | - <filter id="snakeGlow" x="-20%" y="-20%" width="140%" height="140%"> |
| 49 | - <feGaussianBlur stdDeviation="2" result="blur"/> |
| 50 | - <feMerge> |
| 51 | - <feMergeNode in="blur"/> |
| 52 | - <feMergeNode in="SourceGraphic"/> |
| 53 | - </feMerge> |
| 54 | - </filter> |
| 55 | - |
| 56 | - <!-- Inner Shadow for Pit --> |
| 57 | - <filter id="pitShadow" x="-10%" y="-10%" width="120%" height="120%"> |
| 58 | - <feGaussianBlur stdDeviation="4" result="shadow"/> |
| 59 | - <feOffset dx="0" dy="2"/> |
| 60 | - <feComposite in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1"/> |
| 61 | - <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.6 0"/> |
| 62 | - <feMerge> |
| 63 | - <feMergeNode/> |
| 64 | - <feMergeNode in="SourceGraphic"/> |
| 65 | - </feMerge> |
| 66 | - </filter> |
| 67 | - |
| 68 | - <!-- Ambient Light --> |
| 69 | - <filter id="ambientLight"> |
| 70 | - <feSpecularLighting surfaceScale="3" specularConstant="0.8" specularExponent="25" lighting-color="#ffffff" result="specOut"> |
| 71 | - <fePointLight x="100" y="50" z="80"/> |
| 72 | - </feSpecularLighting> |
| 73 | - <feComposite in="SourceGraphic" in2="specOut" operator="arithmetic" k1="0" k2="1" k3="0.3" k4="0"/> |
| 74 | - </filter> |
| 75 | - |
| 76 | - <!-- Hexagon Clip Path --> |
| 77 | - <clipPath id="hexClip"> |
| 78 | - <polygon points="100,8 181.6,55 181.6,145 100,192 18.4,145 18.4,55"/> |
| 79 | - </clipPath> |
| 80 | - |
| 81 | - <!-- Snake Body Texture --> |
| 82 | - <linearGradient id="blueSnakeBody" x1="0%" y1="0%" x2="0%" y2="100%"> |
| 83 | - <stop offset="0%" stop-color="#5a9fd4"/> |
| 84 | - <stop offset="30%" stop-color="#4b8bbe"/> |
| 85 | - <stop offset="70%" stop-color="#306998"/> |
| 86 | - <stop offset="100%" stop-color="#254f73"/> |
| 87 | - </linearGradient> |
| 88 | - |
| 89 | - <linearGradient id="yellowSnakeBody" x1="0%" y1="0%" x2="0%" y2="100%"> |
| 90 | - <stop offset="0%" stop-color="#fff3a3"/> |
| 91 | - <stop offset="30%" stop-color="#ffe873"/> |
| 92 | - <stop offset="70%" stop-color="#ffd43b"/> |
| 93 | - <stop offset="100%" stop-color="#e6b800"/> |
| 94 | - </linearGradient> |
| 95 | - |
| 96 | - <!-- Subtle noise texture --> |
| 97 | - <filter id="noiseTexture"> |
| 98 | - <feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="4" result="noise"/> |
| 99 | - <feColorMatrix type="saturate" values="0"/> |
| 100 | - <feBlend in="SourceGraphic" in2="noise" mode="multiply" result="blended"/> |
| 101 | - <feComposite in="blended" in2="SourceAlpha" operator="in"/> |
| 102 | - </filter> |
| 103 | - </defs> |
| 104 | - |
| 105 | - <!-- Outer Hexagon Border/Glow --> |
| 106 | - <polygon |
| 107 | - points="100,5 184.6,53.5 184.6,146.5 100,195 15.4,146.5 15.4,53.5" |
| 108 | - fill="none" |
| 109 | - stroke="url(#hexBorder)" |
| 110 | - stroke-width="2" |
| 111 | - opacity="0.6" |
| 112 | - /> |
| 113 | - |
| 114 | - <!-- Main Hexagon (The Pit) --> |
| 115 | - <polygon |
| 116 | - points="100,8 181.6,55 181.6,145 100,192 18.4,145 18.4,55" |
| 117 | - fill="url(#pitDepth)" |
| 118 | - stroke="url(#hexBorder)" |
| 119 | - stroke-width="3" |
| 120 | - filter="url(#pitShadow)" |
| 121 | - /> |
| 122 | - |
| 123 | - <!-- Pit Floor Pattern (subtle) --> |
| 124 | - <g clip-path="url(#hexClip)" opacity="0.3"> |
| 125 | - <circle cx="100" cy="130" r="50" fill="none" stroke="#1a2540" stroke-width="1"/> |
| 126 | - <circle cx="100" cy="130" r="35" fill="none" stroke="#1a2540" stroke-width="1"/> |
| 127 | - <circle cx="100" cy="130" r="20" fill="none" stroke="#1a2540" stroke-width="1"/> |
| 128 | - </g> |
| 129 | - |
| 130 | - <!-- Snake Pit Contents --> |
| 131 | - <g clip-path="url(#hexClip)"> |
| 132 | - |
| 133 | - <!-- ===== DEEPEST LAYER - Faded coils at bottom ===== --> |
| 134 | - |
| 135 | - <!-- Blue snake coil in deep background --> |
| 136 | - <g filter="url(#snakeGlow)" opacity="0.35"> |
| 137 | - <path |
| 138 | - d="M 60,148 C 75,155 95,145 110,150 C 125,155 135,148 145,142" |
| 139 | - fill="none" |
| 140 | - stroke="url(#blueSnakeBody)" |
| 141 | - stroke-width="11" |
| 142 | - stroke-linecap="round" |
| 143 | - /> |
| 144 | - </g> |
| 145 | - |
| 146 | - <!-- Yellow snake loop deep --> |
| 147 | - <g filter="url(#snakeGlow)" opacity="0.4"> |
| 148 | - <path |
| 149 | - d="M 140,132 C 130,140 112,130 95,136 C 78,142 68,136 60,128" |
| 150 | - fill="none" |
| 151 | - stroke="url(#yellowSnakeBody)" |
| 152 | - stroke-width="9" |
| 153 | - stroke-linecap="round" |
| 154 | - /> |
| 155 | - </g> |
| 156 | - |
| 157 | - <!-- ===== MID LAYER - More visible snakes ===== --> |
| 158 | - |
| 159 | - <!-- Blue snake winding from bottom-left --> |
| 160 | - <g filter="url(#snakeGlow)" opacity="0.6"> |
| 161 | - <path |
| 162 | - d="M 52,130 C 58,118 72,122 78,110 C 84,98 70,92 62,102 C 54,112 50,120 55,128" |
| 163 | - fill="none" |
| 164 | - stroke="url(#blueSnakeBody)" |
| 165 | - stroke-width="13" |
| 166 | - stroke-linecap="round" |
| 167 | - /> |
| 168 | - </g> |
| 169 | - |
| 170 | - <!-- Yellow snake coiling right side --> |
| 171 | - <g filter="url(#snakeGlow)" opacity="0.65"> |
| 172 | - <path |
| 173 | - d="M 148,125 C 142,112 128,118 124,105 C 120,92 138,88 144,100 C 150,112 152,120 146,128" |
| 174 | - fill="none" |
| 175 | - stroke="url(#yellowSnakeBody)" |
| 176 | - stroke-width="12" |
| 177 | - stroke-linecap="round" |
| 178 | - /> |
| 179 | - </g> |
| 180 | - |
| 181 | - <!-- ===== MAIN SNAKES - Full detail ===== --> |
| 182 | - |
| 183 | - <!-- Main Blue Snake - curving from left, head pointing upper-right --> |
| 184 | - <g> |
| 185 | - <path |
| 186 | - d="M 45,95 |
| 187 | - C 52,82 72,88 82,75 |
| 188 | - C 92,62 85,52 95,45 |
| 189 | - C 105,38 118,48 125,58" |
| 190 | - fill="none" |
| 191 | - stroke="url(#blueSnakeBody)" |
| 192 | - stroke-width="15" |
| 193 | - stroke-linecap="round" |
| 194 | - filter="url(#snakeGlow)" |
| 195 | - /> |
| 196 | - |
| 197 | - <!-- Blue Snake Head - detailed anatomical design --> |
| 198 | - <g transform="translate(122, 52) rotate(25)"> |
| 199 | - <!-- Head base shape (slightly triangular) --> |
| 200 | - <path d="M -2,0 Q 2,-7 10,-6 Q 18,-4 18,0 Q 18,4 10,6 Q 2,7 -2,0 Z" fill="url(#pythonBlue)"/> |
| 201 | - <path d="M -1,0 Q 2,-6 10,-5 Q 16,-3 16,0 Q 16,3 10,5 Q 2,6 -1,0 Z" fill="url(#blueSnakeBody)"/> |
| 202 | - <!-- Brow ridge --> |
| 203 | - <path d="M 4,-4 Q 8,-5.5 12,-4" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="0.8"/> |
| 204 | - <!-- Scale pattern on crown --> |
| 205 | - <ellipse cx="10" cy="-3" rx="3" ry="1.5" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.4"/> |
| 206 | - <ellipse cx="6" cy="-4" rx="2" ry="1" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.3"/> |
| 207 | - <!-- Eye socket --> |
| 208 | - <ellipse cx="5" cy="-1.5" rx="2.8" ry="2.5" fill="#0a0e15"/> |
| 209 | - <!-- Eye --> |
| 210 | - <ellipse cx="5" cy="-1.5" rx="2.2" ry="2" fill="#1a1a1a"/> |
| 211 | - <!-- Slit pupil --> |
| 212 | - <ellipse cx="5" cy="-1.5" rx="0.6" ry="1.8" fill="#ffd43b" filter="url(#eyeGlow)"/> |
| 213 | - <!-- Eye highlights --> |
| 214 | - <circle cx="4.2" cy="-2.2" r="0.4" fill="#ffffff" opacity="0.9"/> |
| 215 | - <circle cx="5.5" cy="-0.8" r="0.2" fill="#ffffff" opacity="0.5"/> |
| 216 | - <!-- Heat pit --> |
| 217 | - <ellipse cx="0" cy="-1" rx="0.6" ry="0.4" fill="#1a2540"/> |
| 218 | - <!-- Nostrils --> |
| 219 | - <circle cx="-1" cy="-0.8" r="0.35" fill="#0a0f1a"/> |
| 220 | - <circle cx="-1" cy="0.8" r="0.35" fill="#0a0f1a"/> |
| 221 | - <!-- Jaw line --> |
| 222 | - <path d="M 2,3 Q 8,4 14,2" fill="none" stroke="rgba(0,0,0,0.2)" stroke-width="0.5"/> |
| 223 | - <!-- Forked tongue --> |
| 224 | - <path d="M -3,0 L -7,0 L -9,-1.5 M -7,0 L -9,1.5" stroke="#dd3344" stroke-width="0.5" stroke-linecap="round" fill="none"/> |
| 225 | - </g> |
| 226 | - </g> |
| 227 | - |
| 228 | - <!-- Main Yellow Snake - curving from bottom-right, head pointing left --> |
| 229 | - <g> |
| 230 | - <path |
| 231 | - d="M 158,120 |
| 232 | - C 145,112 135,125 115,118 |
| 233 | - C 95,111 100,95 85,88 |
| 234 | - C 70,81 55,90 48,78" |
| 235 | - fill="none" |
| 236 | - stroke="url(#yellowSnakeBody)" |
| 237 | - stroke-width="15" |
| 238 | - stroke-linecap="round" |
| 239 | - filter="url(#snakeGlow)" |
| 240 | - /> |
| 241 | - |
| 242 | - <!-- Yellow Snake Head - detailed anatomical design --> |
| 243 | - <g transform="translate(52, 80) rotate(-135)"> |
| 244 | - <!-- Head base shape (slightly triangular) --> |
| 245 | - <path d="M -2,0 Q 2,-7 10,-6 Q 18,-4 18,0 Q 18,4 10,6 Q 2,7 -2,0 Z" fill="url(#pythonYellow)"/> |
| 246 | - <path d="M -1,0 Q 2,-6 10,-5 Q 16,-3 16,0 Q 16,3 10,5 Q 2,6 -1,0 Z" fill="url(#yellowSnakeBody)"/> |
| 247 | - <!-- Brow ridge --> |
| 248 | - <path d="M 4,-4 Q 8,-5.5 12,-4" fill="none" stroke="rgba(0,0,0,0.1)" stroke-width="0.8"/> |
| 249 | - <!-- Scale pattern on crown --> |
| 250 | - <ellipse cx="10" cy="-3" rx="3" ry="1.5" fill="none" stroke="rgba(0,0,0,0.08)" stroke-width="0.4"/> |
| 251 | - <ellipse cx="6" cy="-4" rx="2" ry="1" fill="none" stroke="rgba(0,0,0,0.08)" stroke-width="0.3"/> |
| 252 | - <!-- Eye socket --> |
| 253 | - <ellipse cx="5" cy="-1.5" rx="2.8" ry="2.5" fill="#0a0e15"/> |
| 254 | - <!-- Eye --> |
| 255 | - <ellipse cx="5" cy="-1.5" rx="2.2" ry="2" fill="#1a1a1a"/> |
| 256 | - <!-- Slit pupil --> |
| 257 | - <ellipse cx="5" cy="-1.5" rx="0.6" ry="1.8" fill="#306998" filter="url(#eyeGlow)"/> |
| 258 | - <!-- Eye highlights --> |
| 259 | - <circle cx="4.2" cy="-2.2" r="0.4" fill="#ffffff" opacity="0.9"/> |
| 260 | - <circle cx="5.5" cy="-0.8" r="0.2" fill="#ffffff" opacity="0.5"/> |
| 261 | - <!-- Heat pit --> |
| 262 | - <ellipse cx="0" cy="-1" rx="0.6" ry="0.4" fill="#8b7500"/> |
| 263 | - <!-- Nostrils --> |
| 264 | - <circle cx="-1" cy="-0.8" r="0.35" fill="#8b6914"/> |
| 265 | - <circle cx="-1" cy="0.8" r="0.35" fill="#8b6914"/> |
| 266 | - <!-- Jaw line --> |
| 267 | - <path d="M 2,3 Q 8,4 14,2" fill="none" stroke="rgba(0,0,0,0.15)" stroke-width="0.5"/> |
| 268 | - <!-- Forked tongue --> |
| 269 | - <path d="M -3,0 L -7,0 L -9,-1.5 M -7,0 L -9,1.5" stroke="#dd3344" stroke-width="0.5" stroke-linecap="round" fill="none"/> |
| 270 | - </g> |
| 271 | - </g> |
| 272 | - |
| 273 | - <!-- Third Snake (Blue) - slithering across middle --> |
| 274 | - <g> |
| 275 | - <path |
| 276 | - d="M 35,108 |
| 277 | - C 50,100 65,112 85,102 |
| 278 | - C 105,92 115,105 130,95 |
| 279 | - C 145,85 155,95 162,88" |
| 280 | - fill="none" |
| 281 | - stroke="url(#blueSnakeBody)" |
| 282 | - stroke-width="12" |
| 283 | - stroke-linecap="round" |
| 284 | - filter="url(#snakeGlow)" |
| 285 | - /> |
| 286 | - |
| 287 | - <!-- Third snake head - detailed smaller version --> |
| 288 | - <g transform="translate(159, 86) rotate(20)"> |
| 289 | - <!-- Head base --> |
| 290 | - <path d="M -1,0 Q 1,-5 6,-4.5 Q 11,-3 11,0 Q 11,3 6,4.5 Q 1,5 -1,0 Z" fill="url(#pythonBlue)"/> |
| 291 | - <path d="M 0,0 Q 1.5,-4 6,-3.5 Q 10,-2.5 10,0 Q 10,2.5 6,3.5 Q 1.5,4 0,0 Z" fill="url(#blueSnakeBody)"/> |
| 292 | - <!-- Brow ridge --> |
| 293 | - <path d="M 2,-2.5 Q 5,-3.5 8,-2.5" fill="none" stroke="rgba(255,255,255,0.12)" stroke-width="0.5"/> |
| 294 | - <!-- Eye socket --> |
| 295 | - <ellipse cx="3" cy="-1" rx="1.8" ry="1.6" fill="#0a0e15"/> |
| 296 | - <!-- Eye --> |
| 297 | - <ellipse cx="3" cy="-1" rx="1.4" ry="1.3" fill="#1a1a1a"/> |
| 298 | - <!-- Slit pupil --> |
| 299 | - <ellipse cx="3" cy="-1" rx="0.4" ry="1.1" fill="#ffd43b" filter="url(#eyeGlow)"/> |
| 300 | - <!-- Highlight --> |
| 301 | - <circle cx="2.5" cy="-1.5" r="0.25" fill="#ffffff" opacity="0.85"/> |
| 302 | - <!-- Nostril --> |
| 303 | - <circle cx="-0.5" cy="0" r="0.25" fill="#1a2540"/> |
| 304 | - <!-- Forked tongue --> |
| 305 | - <path d="M -2,0 L -4.5,0 L -6,-1 M -4.5,0 L -6,1" stroke="#dd3344" stroke-width="0.4" stroke-linecap="round" fill="none"/> |
| 306 | - </g> |
| 307 | - </g> |
| 308 | - |
| 309 | - <!-- Fourth Snake (Yellow) - smaller, peeking from upper left --> |
| 310 | - <g opacity="0.85"> |
| 311 | - <path |
| 312 | - d="M 38,72 C 48,65 62,72 72,62 C 82,52 75,42 85,38" |
| 313 | - fill="none" |
| 314 | - stroke="url(#yellowSnakeBody)" |
| 315 | - stroke-width="10" |
| 316 | - stroke-linecap="round" |
| 317 | - filter="url(#snakeGlow)" |
| 318 | - /> |
| 319 | - |
| 320 | - <!-- Fourth snake head - detailed smaller version --> |
| 321 | - <g transform="translate(83, 36) rotate(30)"> |
| 322 | - <!-- Head base --> |
| 323 | - <path d="M -1,0 Q 1,-4.5 5,-4 Q 9,-2.5 9,0 Q 9,2.5 5,4 Q 1,4.5 -1,0 Z" fill="url(#pythonYellow)"/> |
| 324 | - <path d="M 0,0 Q 1,-3.5 5,-3 Q 8,-2 8,0 Q 8,2 5,3 Q 1,3.5 0,0 Z" fill="url(#yellowSnakeBody)"/> |
| 325 | - <!-- Brow ridge --> |
| 326 | - <path d="M 1.5,-2 Q 4,-3 6.5,-2" fill="none" stroke="rgba(0,0,0,0.08)" stroke-width="0.4"/> |
| 327 | - <!-- Eye socket --> |
| 328 | - <ellipse cx="2.5" cy="-0.8" rx="1.5" ry="1.4" fill="#0a0e15"/> |
| 329 | - <!-- Eye --> |
| 330 | - <ellipse cx="2.5" cy="-0.8" rx="1.2" ry="1.1" fill="#1a1a1a"/> |
| 331 | - <!-- Slit pupil --> |
| 332 | - <ellipse cx="2.5" cy="-0.8" rx="0.35" ry="0.9" fill="#306998" filter="url(#eyeGlow)"/> |
| 333 | - <!-- Highlight --> |
| 334 | - <circle cx="2.1" cy="-1.2" r="0.2" fill="#ffffff" opacity="0.85"/> |
| 335 | - <!-- Nostril --> |
| 336 | - <circle cx="-0.3" cy="0" r="0.2" fill="#8b6914"/> |
| 337 | - <!-- Forked tongue --> |
| 338 | - <path d="M -1.5,0 L -3.5,0 L -4.8,-0.8 M -3.5,0 L -4.8,0.8" stroke="#dd3344" stroke-width="0.35" stroke-linecap="round" fill="none"/> |
| 339 | - </g> |
| 340 | - </g> |
| 341 | - |
| 342 | - <!-- Fifth Snake (Blue) - tiny one lurking bottom right --> |
| 343 | - <g opacity="0.7"> |
| 344 | - <path |
| 345 | - d="M 152,130 C 145,136 135,130 128,138 C 122,146 130,152 125,158" |
| 346 | - fill="none" |
| 347 | - stroke="url(#blueSnakeBody)" |
| 348 | - stroke-width="8" |
| 349 | - stroke-linecap="round" |
| 350 | - filter="url(#snakeGlow)" |
| 351 | - /> |
| 352 | - </g> |
| 353 | - |
| 354 | - <!-- Loose coil segment (Yellow) in bottom left --> |
| 355 | - <g opacity="0.55"> |
| 356 | - <path |
| 357 | - d="M 62,148 C 72,142 80,150 88,144 C 96,138 92,132 98,128" |
| 358 | - fill="none" |
| 359 | - stroke="url(#yellowSnakeBody)" |
| 360 | - stroke-width="10" |
| 361 | - stroke-linecap="round" |
| 362 | - filter="url(#snakeGlow)" |
| 363 | - /> |
| 364 | - </g> |
| 365 | - |
| 366 | - <!-- Background tail fragment --> |
| 367 | - <g opacity="0.45"> |
| 368 | - <path |
| 369 | - d="M 115,155 C 108,148 100,155 90,150" |
| 370 | - fill="none" |
| 371 | - stroke="url(#blueSnakeBody)" |
| 372 | - stroke-width="7" |
| 373 | - stroke-linecap="round" |
| 374 | - /> |
| 375 | - </g> |
| 376 | - |
| 377 | - </g> |
| 378 | - |
| 379 | - <!-- Subtle highlight on hexagon edge --> |
| 380 | - <polygon |
| 381 | - points="100,8 181.6,55 181.6,145 100,192 18.4,145 18.4,55" |
| 382 | - fill="none" |
| 383 | - stroke="url(#hexBorder)" |
| 384 | - stroke-width="1.5" |
| 385 | - opacity="0.4" |
| 386 | - /> |
| 387 | - </svg> |
| \ No newline at end of file |
| @@ -1,512 +0,0 @@ | |
| 1 | - # Snakepit Configuration Guide |
| 2 | - |
| 3 | - This guide covers all configuration options for Snakepit, from simple single-pool setups to advanced multi-pool deployments with different worker profiles. |
| 4 | - |
| 5 | - --- |
| 6 | - |
| 7 | - ## Table of Contents |
| 8 | - |
| 9 | - 1. [Configuration Formats](#configuration-formats) |
| 10 | - 2. [Global Options](#global-options) |
| 11 | - 3. [Pool Configuration](#pool-configuration) |
| 12 | - 4. [Heartbeat Configuration](#heartbeat-configuration) |
| 13 | - 5. [Logging Configuration](#logging-configuration) |
| 14 | - 6. [Python Runtime Configuration](#python-runtime-configuration) |
| 15 | - 7. [Optional Features](#optional-features) |
| 16 | - 8. [Complete Configuration Example](#complete-configuration-example) |
| 17 | - |
| 18 | - --- |
| 19 | - |
| 20 | - ## Configuration Formats |
| 21 | - |
| 22 | - Snakepit supports two configuration formats: legacy (single-pool) and multi-pool (v0.6+). |
| 23 | - |
| 24 | - ### Simple (Legacy) Configuration |
| 25 | - |
| 26 | - For backward compatibility with v0.5.x and single-pool deployments: |
| 27 | - |
| 28 | - ```elixir |
| 29 | - # config/config.exs |
| 30 | - config :snakepit, |
| 31 | - pooling_enabled: true, |
| 32 | - adapter_module: Snakepit.Adapters.GRPCPython, |
| 33 | - pool_size: 100, |
| 34 | - pool_config: %{ |
| 35 | - startup_batch_size: 8, |
| 36 | - startup_batch_delay_ms: 750, |
| 37 | - max_workers: 1000 |
| 38 | - } |
| 39 | - ``` |
| 40 | - |
| 41 | - This format creates a single pool named `:default` with the specified settings. |
| 42 | - |
| 43 | - ### Multi-Pool Configuration (v0.6+) |
| 44 | - |
| 45 | - For advanced deployments with multiple pools, each with different profiles: |
| 46 | - |
| 47 | - ```elixir |
| 48 | - # config/config.exs |
| 49 | - config :snakepit, |
| 50 | - pools: [ |
| 51 | - %{ |
| 52 | - name: :default, |
| 53 | - worker_profile: :process, |
| 54 | - pool_size: 100, |
| 55 | - adapter_module: Snakepit.Adapters.GRPCPython |
| 56 | - }, |
| 57 | - %{ |
| 58 | - name: :ml_inference, |
| 59 | - worker_profile: :thread, |
| 60 | - pool_size: 4, |
| 61 | - threads_per_worker: 16, |
| 62 | - adapter_module: Snakepit.Adapters.GRPCPython, |
| 63 | - adapter_args: ["--adapter", "myapp.ml.InferenceAdapter"] |
| 64 | - } |
| 65 | - ] |
| 66 | - ``` |
| 67 | - |
| 68 | - This creates two pools: `:default` for general tasks and `:ml_inference` for CPU-bound ML workloads. |
| 69 | - |
| 70 | - --- |
| 71 | - |
| 72 | - ## Global Options |
| 73 | - |
| 74 | - These options apply to all pools or the Snakepit application as a whole. |
| 75 | - |
| 76 | - | Option | Type | Default | Description | |
| 77 | - |--------|------|---------|-------------| |
| 78 | - | `pooling_enabled` | `boolean()` | `false` | Enable or disable worker pooling. Set to `true` for normal operation. | |
| 79 | - | `adapter_module` | `module()` | `nil` | Default adapter module for pools that do not specify one. | |
| 80 | - | `pool_size` | `pos_integer()` | `System.schedulers_online() * 2` | Default pool size. Typically 2x CPU cores. | |
| 81 | - | `capacity_strategy` | `:pool \| :profile \| :hybrid` | `:pool` | How worker capacity is managed across pools. | |
| 82 | - | `pool_startup_timeout` | `pos_integer()` | `10000` | Maximum time (ms) to wait for a worker to start. | |
| 83 | - | `pool_queue_timeout` | `pos_integer()` | `5000` | Maximum time (ms) a request waits in queue. | |
| 84 | - | `pool_max_queue_size` | `pos_integer()` | `1000` | Maximum queued requests before rejecting new ones. | |
| 85 | - | `grpc_port` | `pos_integer()` | `50051` | Port for the Elixir gRPC server (Python-to-Elixir calls). | |
| 86 | - | `grpc_host` | `String.t()` | `"localhost"` | Host for gRPC connections. | |
| 87 | - | `graceful_shutdown_timeout_ms` | `pos_integer()` | `6000` | Time (ms) to wait for Python to terminate gracefully before SIGKILL. | |
| 88 | - |
| 89 | - ### Capacity Strategies |
| 90 | - |
| 91 | - | Strategy | Description | |
| 92 | - |----------|-------------| |
| 93 | - | `:pool` | Each pool manages its own capacity independently. Default and simplest option. | |
| 94 | - | `:profile` | Workers of the same profile share capacity across pools. | |
| 95 | - | `:hybrid` | Combination of pool and profile strategies for complex deployments. | |
| 96 | - |
| 97 | - --- |
| 98 | - |
| 99 | - ## Pool Configuration |
| 100 | - |
| 101 | - Each pool can be configured independently with these options. |
| 102 | - |
| 103 | - ### Required Fields |
| 104 | - |
| 105 | - | Option | Type | Description | |
| 106 | - |--------|------|-------------| |
| 107 | - | `name` | `atom()` | Unique pool identifier. Use `:default` for the primary pool. | |
| 108 | - |
| 109 | - ### Profile Selection |
| 110 | - |
| 111 | - | Option | Type | Default | Description | |
| 112 | - |--------|------|---------|-------------| |
| 113 | - | `worker_profile` | `:process \| :thread` | `:process` | Worker execution model. See [Worker Profiles Guide](worker-profiles.md). | |
| 114 | - |
| 115 | - ### Common Pool Options |
| 116 | - |
| 117 | - | Option | Type | Default | Description | |
| 118 | - |--------|------|---------|-------------| |
| 119 | - | `pool_size` | `pos_integer()` | Global setting | Number of workers in this pool. | |
| 120 | - | `adapter_module` | `module()` | Global setting | Adapter module for this pool. | |
| 121 | - | `adapter_args` | `list(String.t())` | `[]` | CLI arguments passed to the Python server. | |
| 122 | - | `adapter_env` | `list({String.t(), String.t()})` | `[]` | Environment variables for Python processes. | |
| 123 | - | `adapter_spec` | `String.t()` | `nil` | Python adapter module path (e.g., `"myapp.adapters.MyAdapter"`). | |
| 124 | - |
| 125 | - ### Process Profile Options |
| 126 | - |
| 127 | - These options apply when `worker_profile: :process`: |
| 128 | - |
| 129 | - | Option | Type | Default | Description | |
| 130 | - |--------|------|---------|-------------| |
| 131 | - | `startup_batch_size` | `pos_integer()` | `8` | Workers started per batch during pool initialization. | |
| 132 | - | `startup_batch_delay_ms` | `non_neg_integer()` | `750` | Delay between startup batches (ms). Reduces system load during startup. | |
| 133 | - |
| 134 | - ### Thread Profile Options |
| 135 | - |
| 136 | - These options apply when `worker_profile: :thread`: |
| 137 | - |
| 138 | - | Option | Type | Default | Description | |
| 139 | - |--------|------|---------|-------------| |
| 140 | - | `threads_per_worker` | `pos_integer()` | `10` | Thread pool size per Python process. Total capacity = `pool_size * threads_per_worker`. | |
| 141 | - | `thread_safety_checks` | `boolean()` | `false` | Enable runtime thread safety validation. Useful for development. | |
| 142 | - |
| 143 | - ### Worker Lifecycle Options |
| 144 | - |
| 145 | - | Option | Type | Default | Description | |
| 146 | - |--------|------|---------|-------------| |
| 147 | - | `worker_ttl` | `:infinity \| {value, unit}` | `:infinity` | Maximum worker lifetime before recycling. | |
| 148 | - | `worker_max_requests` | `:infinity \| pos_integer()` | `:infinity` | Maximum requests before recycling a worker. | |
| 149 | - |
| 150 | - **TTL Units:** |
| 151 | - |
| 152 | - | Unit | Example | |
| 153 | - |------|---------| |
| 154 | - | `:seconds` | `{3600, :seconds}` - 1 hour | |
| 155 | - | `:minutes` | `{60, :minutes}` - 1 hour | |
| 156 | - | `:hours` | `{1, :hours}` - 1 hour | |
| 157 | - |
| 158 | - Worker recycling helps prevent memory leaks and ensures fresh worker state. |
| 159 | - |
| 160 | - --- |
| 161 | - |
| 162 | - ## Heartbeat Configuration |
| 163 | - |
| 164 | - Heartbeats detect unresponsive workers and trigger automatic restarts. |
| 165 | - |
| 166 | - ### Global Heartbeat Config |
| 167 | - |
| 168 | - ```elixir |
| 169 | - config :snakepit, |
| 170 | - heartbeat: %{ |
| 171 | - enabled: true, |
| 172 | - ping_interval_ms: 2000, |
| 173 | - timeout_ms: 10000, |
| 174 | - max_missed_heartbeats: 3, |
| 175 | - initial_delay_ms: 0, |
| 176 | - dependent: true |
| 177 | - } |
| 178 | - ``` |
| 179 | - |
| 180 | - ### Per-Pool Heartbeat Config |
| 181 | - |
| 182 | - ```elixir |
| 183 | - %{ |
| 184 | - name: :ml_pool, |
| 185 | - heartbeat: %{ |
| 186 | - enabled: true, |
| 187 | - ping_interval_ms: 10000, |
| 188 | - timeout_ms: 30000, |
| 189 | - max_missed_heartbeats: 2 |
| 190 | - } |
| 191 | - } |
| 192 | - ``` |
| 193 | - |
| 194 | - ### Heartbeat Options |
| 195 | - |
| 196 | - | Option | Type | Default | Description | |
| 197 | - |--------|------|---------|-------------| |
| 198 | - | `enabled` | `boolean()` | `true` | Enable heartbeat monitoring. | |
| 199 | - | `ping_interval_ms` | `pos_integer()` | `2000` | Interval between heartbeat pings. | |
| 200 | - | `timeout_ms` | `pos_integer()` | `10000` | Maximum time to wait for heartbeat response. | |
| 201 | - | `max_missed_heartbeats` | `pos_integer()` | `3` | Missed heartbeats before declaring worker dead. | |
| 202 | - | `initial_delay_ms` | `non_neg_integer()` | `0` | Delay before first heartbeat ping. | |
| 203 | - | `dependent` | `boolean()` | `true` | Whether worker terminates if heartbeat monitor dies. | |
| 204 | - |
| 205 | - ### Tuning Guidelines |
| 206 | - |
| 207 | - - **Fast detection**: Lower `ping_interval_ms` and `max_missed_heartbeats` |
| 208 | - - **Reduce overhead**: Higher `ping_interval_ms` for stable workloads |
| 209 | - - **Long operations**: Increase `timeout_ms` if workers run long computations |
| 210 | - - **ML workloads**: Use `ping_interval_ms: 10000` or higher since inference can block |
| 211 | - |
| 212 | - --- |
| 213 | - |
| 214 | - ## Logging Configuration |
| 215 | - |
| 216 | - Snakepit uses its own logger for internal operations. |
| 217 | - |
| 218 | - ### Log Level |
| 219 | - |
| 220 | - ```elixir |
| 221 | - config :snakepit, |
| 222 | - log_level: :info # :debug | :info | :warning | :error | :none |
| 223 | - ``` |
| 224 | - |
| 225 | - | Level | Description | |
| 226 | - |-------|-------------| |
| 227 | - | `:debug` | Verbose output including worker lifecycle, gRPC calls, heartbeats | |
| 228 | - | `:info` | Normal operation messages | |
| 229 | - | `:warning` | Potential issues that do not stop operation | |
| 230 | - | `:error` | Errors that affect functionality | |
| 231 | - | `:none` | Disable all Snakepit logging | |
| 232 | - |
| 233 | - ### Log Categories |
| 234 | - |
| 235 | - Fine-grained control over logging categories: |
| 236 | - |
| 237 | - ```elixir |
| 238 | - config :snakepit, |
| 239 | - log_level: :info, |
| 240 | - log_categories: %{ |
| 241 | - pool: :debug, # Pool operations |
| 242 | - worker: :debug, # Worker lifecycle |
| 243 | - heartbeat: :info, # Heartbeat monitoring |
| 244 | - grpc: :warning # gRPC communication |
| 245 | - } |
| 246 | - ``` |
| 247 | - |
| 248 | - ### Python-Side Logging |
| 249 | - |
| 250 | - The Python bridge respects the `SNAKEPIT_LOG_LEVEL` environment variable: |
| 251 | - |
| 252 | - ```elixir |
| 253 | - %{ |
| 254 | - name: :default, |
| 255 | - adapter_env: [{"SNAKEPIT_LOG_LEVEL", "info"}] |
| 256 | - } |
| 257 | - ``` |
| 258 | - |
| 259 | - --- |
| 260 | - |
| 261 | - ## Python Runtime Configuration |
| 262 | - |
| 263 | - Configure how Python interpreters are discovered and managed. |
| 264 | - |
| 265 | - ### Interpreter Selection |
| 266 | - |
| 267 | - ```elixir |
| 268 | - config :snakepit, |
| 269 | - python_executable: "/path/to/python3" |
| 270 | - ``` |
| 271 | - |
| 272 | - Or use environment variable (takes precedence): |
| 273 | - |
| 274 | - ```bash |
| 275 | - export SNAKEPIT_PYTHON="/path/to/python3" |
| 276 | - ``` |
| 277 | - |
| 278 | - ### Runtime Strategy |
| 279 | - |
| 280 | - ```elixir |
| 281 | - config :snakepit, |
| 282 | - python_runtime: %{ |
| 283 | - strategy: :venv, # :system | :venv | :managed |
| 284 | - managed: false, |
| 285 | - version: "3.12" |
| 286 | - } |
| 287 | - ``` |
| 288 | - |
| 289 | - | Strategy | Description | |
| 290 | - |----------|-------------| |
| 291 | - | `:system` | Use system Python interpreter | |
| 292 | - | `:venv` | Use project virtual environment (`.venv/bin/python3`) | |
| 293 | - | `:managed` | Let Snakepit manage Python version (experimental) | |
| 294 | - |
| 295 | - ### Environment Variables per Pool |
| 296 | - |
| 297 | - ```elixir |
| 298 | - %{ |
| 299 | - name: :ml_pool, |
| 300 | - adapter_env: [ |
| 301 | - # Control threading in numerical libraries |
| 302 | - {"OPENBLAS_NUM_THREADS", "1"}, |
| 303 | - {"MKL_NUM_THREADS", "1"}, |
| 304 | - {"OMP_NUM_THREADS", "1"}, |
| 305 | - {"NUMEXPR_NUM_THREADS", "1"}, |
| 306 | - |
| 307 | - # GPU configuration |
| 308 | - {"CUDA_VISIBLE_DEVICES", "0"}, |
| 309 | - |
| 310 | - # Python settings |
| 311 | - {"PYTHONUNBUFFERED", "1"}, |
| 312 | - {"SNAKEPIT_LOG_LEVEL", "warning"} |
| 313 | - ] |
| 314 | - } |
| 315 | - ``` |
| 316 | - |
| 317 | - --- |
| 318 | - |
| 319 | - ## Optional Features |
| 320 | - |
| 321 | - ### Zero-Copy Data Transfer |
| 322 | - |
| 323 | - Enable zero-copy for large binary data: |
| 324 | - |
| 325 | - ```elixir |
| 326 | - config :snakepit, |
| 327 | - zero_copy: %{ |
| 328 | - enabled: true, |
| 329 | - threshold_bytes: 1_048_576 # 1 MB |
| 330 | - } |
| 331 | - ``` |
| 332 | - |
| 333 | - Zero-copy is beneficial for ML workloads with large tensors. |
| 334 | - |
| 335 | - ### Crash Barrier |
| 336 | - |
| 337 | - Limit restart attempts for frequently crashing workers: |
| 338 | - |
| 339 | - ```elixir |
| 340 | - config :snakepit, |
| 341 | - crash_barrier: %{ |
| 342 | - enabled: true, |
| 343 | - max_restarts: 5, |
| 344 | - window_seconds: 60 |
| 345 | - } |
| 346 | - ``` |
| 347 | - |
| 348 | - If a worker restarts more than `max_restarts` times within `window_seconds`, it is permanently removed from the pool. |
| 349 | - |
| 350 | - ### Circuit Breaker |
| 351 | - |
| 352 | - Prevent cascading failures: |
| 353 | - |
| 354 | - ```elixir |
| 355 | - config :snakepit, |
| 356 | - circuit_breaker: %{ |
| 357 | - enabled: true, |
| 358 | - failure_threshold: 5, |
| 359 | - reset_timeout_ms: 30000 |
| 360 | - } |
| 361 | - ``` |
| 362 | - |
| 363 | - After `failure_threshold` consecutive failures, the circuit opens and requests fail fast for `reset_timeout_ms`. |
| 364 | - |
| 365 | - --- |
| 366 | - |
| 367 | - ## Complete Configuration Example |
| 368 | - |
| 369 | - Here is a production-ready configuration demonstrating all major options: |
| 370 | - |
| 371 | - ```elixir |
| 372 | - # config/config.exs |
| 373 | - config :snakepit, |
| 374 | - # Global settings |
| 375 | - pooling_enabled: true, |
| 376 | - pool_startup_timeout: 30_000, |
| 377 | - pool_queue_timeout: 10_000, |
| 378 | - pool_max_queue_size: 5000, |
| 379 | - grpc_port: 50051, |
| 380 | - |
| 381 | - # Logging |
| 382 | - log_level: :info, |
| 383 | - log_categories: %{ |
| 384 | - pool: :info, |
| 385 | - worker: :warning, |
| 386 | - heartbeat: :warning, |
| 387 | - grpc: :warning |
| 388 | - }, |
| 389 | - |
| 390 | - # Global heartbeat defaults |
| 391 | - heartbeat: %{ |
| 392 | - enabled: true, |
| 393 | - ping_interval_ms: 5000, |
| 394 | - timeout_ms: 15000, |
| 395 | - max_missed_heartbeats: 3 |
| 396 | - }, |
| 397 | - |
| 398 | - # Multiple pools |
| 399 | - pools: [ |
| 400 | - # Default pool for I/O-bound tasks |
| 401 | - %{ |
| 402 | - name: :default, |
| 403 | - worker_profile: :process, |
| 404 | - pool_size: 50, |
| 405 | - adapter_module: Snakepit.Adapters.GRPCPython, |
| 406 | - adapter_args: ["--adapter", "myapp.adapters.GeneralAdapter"], |
| 407 | - adapter_env: [ |
| 408 | - {"OPENBLAS_NUM_THREADS", "1"}, |
| 409 | - {"OMP_NUM_THREADS", "1"} |
| 410 | - ], |
| 411 | - startup_batch_size: 10, |
| 412 | - startup_batch_delay_ms: 500 |
| 413 | - }, |
| 414 | - |
| 415 | - # ML inference pool (CPU-bound, thread profile) |
| 416 | - %{ |
| 417 | - name: :ml_inference, |
| 418 | - worker_profile: :thread, |
| 419 | - pool_size: 4, |
| 420 | - threads_per_worker: 8, # 32 total capacity |
| 421 | - adapter_module: Snakepit.Adapters.GRPCPython, |
| 422 | - adapter_args: ["--adapter", "myapp.ml.InferenceAdapter"], |
| 423 | - adapter_env: [ |
| 424 | - {"OPENBLAS_NUM_THREADS", "8"}, |
| 425 | - {"OMP_NUM_THREADS", "8"}, |
| 426 | - {"CUDA_VISIBLE_DEVICES", "0"}, |
| 427 | - {"PYTORCH_CUDA_ALLOC_CONF", "max_split_size_mb:512"} |
| 428 | - ], |
| 429 | - thread_safety_checks: false, |
| 430 | - worker_ttl: {1800, :seconds}, |
| 431 | - worker_max_requests: 10000, |
| 432 | - heartbeat: %{ |
| 433 | - enabled: true, |
| 434 | - ping_interval_ms: 10000, |
| 435 | - timeout_ms: 60000, |
| 436 | - max_missed_heartbeats: 2 |
| 437 | - } |
| 438 | - }, |
| 439 | - |
| 440 | - # Background processing pool |
| 441 | - %{ |
| 442 | - name: :background, |
| 443 | - worker_profile: :process, |
| 444 | - pool_size: 10, |
| 445 | - adapter_module: Snakepit.Adapters.GRPCPython, |
| 446 | - adapter_args: ["--adapter", "myapp.adapters.BackgroundAdapter"], |
| 447 | - adapter_env: [ |
| 448 | - {"SNAKEPIT_LOG_LEVEL", "warning"} |
| 449 | - ], |
| 450 | - worker_ttl: {3600, :seconds} |
| 451 | - } |
| 452 | - ], |
| 453 | - |
| 454 | - # Optional features |
| 455 | - crash_barrier: %{ |
| 456 | - enabled: true, |
| 457 | - max_restarts: 10, |
| 458 | - window_seconds: 300 |
| 459 | - } |
| 460 | - ``` |
| 461 | - |
| 462 | - ### Environment-Specific Overrides |
| 463 | - |
| 464 | - ```elixir |
| 465 | - # config/prod.exs |
| 466 | - config :snakepit, |
| 467 | - log_level: :warning, |
| 468 | - pool_max_queue_size: 10000 |
| 469 | - |
| 470 | - # config/dev.exs |
| 471 | - config :snakepit, |
| 472 | - log_level: :debug, |
| 473 | - pool_size: 4 |
| 474 | - |
| 475 | - # config/test.exs |
| 476 | - config :snakepit, |
| 477 | - pooling_enabled: false |
| 478 | - ``` |
| 479 | - |
| 480 | - --- |
| 481 | - |
| 482 | - ## Validation |
| 483 | - |
| 484 | - Verify your configuration with the doctor task: |
| 485 | - |
| 486 | - ```bash |
| 487 | - mix snakepit.doctor |
| 488 | - ``` |
| 489 | - |
| 490 | - At runtime, check pool status: |
| 491 | - |
| 492 | - ```elixir |
| 493 | - iex> Snakepit.get_stats() |
| 494 | - %{ |
| 495 | - requests: 15432, |
| 496 | - queued: 5, |
| 497 | - errors: 12, |
| 498 | - queue_timeouts: 3, |
| 499 | - pool_saturated: 0, |
| 500 | - workers: 54, |
| 501 | - available: 49, |
| 502 | - busy: 5 |
| 503 | - } |
| 504 | - ``` |
| 505 | - |
| 506 | - --- |
| 507 | - |
| 508 | - ## Related Guides |
| 509 | - |
| 510 | - - [Getting Started](getting-started.md) - Installation and first steps |
| 511 | - - [Worker Profiles](worker-profiles.md) - Process vs Thread profiles |
| 512 | - - [Production](production.md) - Performance tuning and deployment checklist |
Loading more files…