[{"data":1,"prerenderedAt":1367},["ShallowReactive",2],{"navigation_docs_en":3,"-en-concepts-application-lifecycle":342,"-en-concepts-application-lifecycle-surround":1362},[4,45,61,96,132,166,196,217,241,265],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F10.reference",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F10.reference\u002F1.configuration","i-lucide-settings",{"title":57,"path":58,"stem":59,"icon":60},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F10.reference\u002F2.dependencies","i-lucide-package",{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[67,72,77,82,86,91],{"title":68,"path":69,"stem":70,"icon":71},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture","i-lucide-layers",{"title":73,"path":74,"stem":75,"icon":76},"Application lifecycle","\u002Fen\u002Fconcepts\u002Fapplication-lifecycle","en\u002F2.concepts\u002F2.application-lifecycle","i-lucide-power",{"title":78,"path":79,"stem":80,"icon":81},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F3.error-model","i-lucide-shield-alert",{"title":52,"path":83,"stem":84,"icon":85},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F4.configuration","i-lucide-settings-2",{"title":87,"path":88,"stem":89,"icon":90},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F5.codegen-pipeline","i-lucide-file-json",{"title":92,"path":93,"stem":94,"icon":95},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F6.design-patterns","i-lucide-puzzle",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":44},"HTTP & Routing","i-lucide-globe","\u002Fen\u002Fhttp","en\u002F3.http",[102,107,112,117,122,127],{"title":103,"path":104,"stem":105,"icon":106},"The server core","\u002Fen\u002Fhttp\u002Fserver","en\u002F3.http\u002F1.server","i-lucide-server",{"title":108,"path":109,"stem":110,"icon":111},"The Fiber engine","\u002Fen\u002Fhttp\u002Ffiber","en\u002F3.http\u002F2.fiber","i-lucide-zap",{"title":113,"path":114,"stem":115,"icon":116},"The chi engine","\u002Fen\u002Fhttp\u002Fchi","en\u002F3.http\u002F3.chi","i-lucide-route",{"title":118,"path":119,"stem":120,"icon":121},"Error responses","\u002Fen\u002Fhttp\u002Ferror-responses","en\u002F3.http\u002F4.error-responses","i-lucide-octagon-alert",{"title":123,"path":124,"stem":125,"icon":126},"Validation","\u002Fen\u002Fhttp\u002Fvalidation","en\u002F3.http\u002F5.validation","i-lucide-badge-check",{"title":128,"path":129,"stem":130,"icon":131},"Pagination","\u002Fen\u002Fhttp\u002Fpagination","en\u002F3.http\u002F6.pagination","i-lucide-list-ordered",{"title":133,"icon":134,"path":135,"stem":136,"children":137,"page":44},"Database","i-lucide-database","\u002Fen\u002Fdatabase","en\u002F4.database",[138,142,147,151,156,161],{"title":139,"path":140,"stem":141,"icon":134},"Overview","\u002Fen\u002Fdatabase\u002Foverview","en\u002F4.database\u002F1.overview",{"title":143,"path":144,"stem":145,"icon":146},"pgx","\u002Fen\u002Fdatabase\u002Fpgx","en\u002F4.database\u002F2.pgx","i-lucide-plug",{"title":148,"path":149,"stem":150,"icon":71},"bun","\u002Fen\u002Fdatabase\u002Fbun","en\u002F4.database\u002F3.bun",{"title":152,"path":153,"stem":154,"icon":155},"Transactions","\u002Fen\u002Fdatabase\u002Ftransactions","en\u002F4.database\u002F4.transactions","i-lucide-git-merge",{"title":157,"path":158,"stem":159,"icon":160},"Migrations","\u002Fen\u002Fdatabase\u002Fmigrations","en\u002F4.database\u002F5.migrations","i-lucide-file-stack",{"title":162,"path":163,"stem":164,"icon":165},"Seeders","\u002Fen\u002Fdatabase\u002Fseeding","en\u002F4.database\u002F6.seeding","i-lucide-sprout",{"title":167,"icon":168,"path":169,"stem":170,"children":171,"page":44},"Async & Background","i-lucide-workflow","\u002Fen\u002Fasync","en\u002F5.async",[172,176,181,186,191],{"title":173,"path":174,"stem":175,"icon":131},"Queue","\u002Fen\u002Fasync\u002Fqueue","en\u002F5.async\u002F1.queue",{"title":177,"path":178,"stem":179,"icon":180},"Events","\u002Fen\u002Fasync\u002Fevents","en\u002F5.async\u002F2.events","i-lucide-radio",{"title":182,"path":183,"stem":184,"icon":185},"Outbox","\u002Fen\u002Fasync\u002Foutbox","en\u002F5.async\u002F3.outbox","i-lucide-inbox",{"title":187,"path":188,"stem":189,"icon":190},"Scheduling","\u002Fen\u002Fasync\u002Fscheduler","en\u002F5.async\u002F4.scheduler","i-lucide-calendar-clock",{"title":192,"path":193,"stem":194,"icon":195},"Worker","\u002Fen\u002Fasync\u002Fworker","en\u002F5.async\u002F5.worker","i-lucide-cog",{"title":197,"icon":198,"path":199,"stem":200,"children":201,"page":44},"Security","i-lucide-shield-check","\u002Fen\u002Fsecurity","en\u002F6.security",[202,207,212],{"title":203,"path":204,"stem":205,"icon":206},"Authentication","\u002Fen\u002Fsecurity\u002Fauthentication","en\u002F6.security\u002F1.authentication","i-lucide-key-round",{"title":208,"path":209,"stem":210,"icon":211},"RBAC","\u002Fen\u002Fsecurity\u002Frbac","en\u002F6.security\u002F2.rbac","i-lucide-users",{"title":213,"path":214,"stem":215,"icon":216},"Policies","\u002Fen\u002Fsecurity\u002Fpolicies","en\u002F6.security\u002F3.policies","i-lucide-gavel",{"title":218,"icon":219,"path":220,"stem":221,"children":222,"page":44},"Observability","i-lucide-activity","\u002Fen\u002Fobservability","en\u002F7.observability",[223,226,231,236],{"title":139,"path":224,"stem":225,"icon":219},"\u002Fen\u002Fobservability\u002Foverview","en\u002F7.observability\u002F1.overview",{"title":227,"path":228,"stem":229,"icon":230},"Logging","\u002Fen\u002Fobservability\u002Flogging","en\u002F7.observability\u002F2.logging","i-lucide-scroll-text",{"title":232,"path":233,"stem":234,"icon":235},"OpenTelemetry","\u002Fen\u002Fobservability\u002Fopentelemetry","en\u002F7.observability\u002F3.opentelemetry","i-lucide-radar",{"title":237,"path":238,"stem":239,"icon":240},"Sentry","\u002Fen\u002Fobservability\u002Fsentry","en\u002F7.observability\u002F4.sentry","i-lucide-bug",{"title":242,"icon":243,"path":244,"stem":245,"children":246,"page":44},"Storage & Mail","i-lucide-hard-drive","\u002Fen\u002Fstorage-mail","en\u002F8.storage-mail",[247,251,256,261],{"title":248,"path":249,"stem":250,"icon":243},"Storage","\u002Fen\u002Fstorage-mail\u002Fstorage","en\u002F8.storage-mail\u002F1.storage",{"title":252,"path":253,"stem":254,"icon":255},"Email","\u002Fen\u002Fstorage-mail\u002Fmail","en\u002F8.storage-mail\u002F2.mail","i-lucide-mail",{"title":257,"path":258,"stem":259,"icon":260},"Notifications","\u002Fen\u002Fstorage-mail\u002Fnotifications","en\u002F8.storage-mail\u002F3.notifications","i-lucide-bell",{"title":262,"path":263,"stem":264,"icon":180},"Realtime","\u002Fen\u002Fstorage-mail\u002Frealtime","en\u002F8.storage-mail\u002F4.realtime",{"title":266,"icon":267,"path":268,"stem":269,"children":270,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F9.cli",[271,274,279,284,289,294,299,303,308,313,318,323,328,333,337],{"title":139,"path":272,"stem":273,"icon":267},"\u002Fen\u002Fcli\u002Foverview","en\u002F9.cli\u002F1.overview",{"title":275,"path":276,"stem":277,"icon":278},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F9.cli\u002F10.add-db","i-lucide-database-zap",{"title":280,"path":281,"stem":282,"icon":283},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F9.cli\u002F11.add-compose","i-lucide-container",{"title":285,"path":286,"stem":287,"icon":288},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F9.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":290,"path":291,"stem":292,"icon":293},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F9.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":295,"path":296,"stem":297,"icon":298},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F9.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":300,"path":301,"stem":302,"icon":165},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F9.cli\u002F15.add-seeder",{"title":304,"path":305,"stem":306,"icon":307},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F9.cli\u002F2.new-project","i-lucide-folder-plus",{"title":309,"path":310,"stem":311,"icon":312},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F9.cli\u002F3.new-module","i-lucide-blocks",{"title":314,"path":315,"stem":316,"icon":317},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F9.cli\u002F4.add-surface","i-lucide-layers-2",{"title":319,"path":320,"stem":321,"icon":322},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F9.cli\u002F5.add-core","i-lucide-box",{"title":324,"path":325,"stem":326,"icon":327},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F9.cli\u002F6.add-handler","i-lucide-webhook",{"title":329,"path":330,"stem":331,"icon":332},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F9.cli\u002F7.new-migration","i-lucide-file-plus",{"title":334,"path":335,"stem":336,"icon":195},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F9.cli\u002F8.worker-generators",{"title":338,"path":339,"stem":340,"icon":341},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F9.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"id":343,"title":73,"body":344,"description":1355,"extension":1356,"links":1357,"meta":1358,"navigation":1359,"path":74,"seo":1360,"stem":75,"__hash__":1361},"docs_en\u002Fen\u002F2.concepts\u002F2.application-lifecycle.md",{"type":345,"value":346,"toc":1347},"minimark",[347,365,374,379,385,633,639,746,751,762,769,773,784,892,910,916,920,931,1250,1277,1292,1296,1334,1343],[348,349,350,351,355,356,360,361,364],"p",{},"In PHP the process lifecycle was not your problem: php-fpm started the worker, ran the request, and on deploy the supervisor handled the swap. In Go your binary ",[352,353,354],"strong",{},"is the server",": it has to watch for ",[357,358,359],"code",{},"SIGTERM",", wait out in-flight requests, close the connection pool and flush telemetry before exiting. That is typically the ~60 lines of boilerplate copied into every service's ",[357,362,363],{},"main.go",", and gotten slightly wrong in a different way each time.",[348,366,367,368,373],{},"The kit writes this once, in the ",[352,369,370],{},[357,371,372],{},"app"," package, which makes the topic a non-topic again: in generated projects graceful shutdown is simply there, and now you can also see what happens.",[375,376,378],"h2",{"id":377},"apprun-and-params","app.Run and Params",[348,380,381,382,384],{},"The ",[357,383,372],{}," package is engine-agnostic: it knows neither HTTP nor any other transport. It has a single entry point:",[386,387,392],"pre",{"className":388,"code":389,"language":390,"meta":391,"style":391},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Closer is a named cleanup function executed during graceful shutdown.\ntype Closer struct {\n    Name string\n    Fn   func(context.Context) error\n}\n\n\u002F\u002F Params describes one process run.\ntype Params struct {\n    ListenAddr      string                          \u002F\u002F startup log only\n    ShutdownTimeout time.Duration                   \u002F\u002F budget for the in-flight drain\n    Serve           func() error                    \u002F\u002F blocking listen; runs in a goroutine\n    Shutdown        func(context.Context) error     \u002F\u002F stop accepting, drain in-flight\n    Closers         []Closer                        \u002F\u002F ordered cleanup list\n    Flush           func(context.Context) error     \u002F\u002F telemetry flush, very last\n}\n\nfunc Run(ctx context.Context, p Params) error\n","go","",[357,393,394,403,420,431,455,461,468,474,486,498,515,530,551,566,587,592,597],{"__ignoreMap":391},[395,396,399],"span",{"class":397,"line":398},"line",1,[395,400,402],{"class":401},"sHwdD","\u002F\u002F Closer is a named cleanup function executed during graceful shutdown.\n",[395,404,406,410,414,417],{"class":397,"line":405},2,[395,407,409],{"class":408},"sMK4o","type",[395,411,413],{"class":412},"sBMFI"," Closer",[395,415,416],{"class":408}," struct",[395,418,419],{"class":408}," {\n",[395,421,423,427],{"class":397,"line":422},3,[395,424,426],{"class":425},"sTEyZ","    Name ",[395,428,430],{"class":429},"spNyl","string\n",[395,432,434,437,440,443,446,449,452],{"class":397,"line":433},4,[395,435,436],{"class":425},"    Fn   ",[395,438,439],{"class":408},"func(",[395,441,442],{"class":412},"context",[395,444,445],{"class":408},".",[395,447,448],{"class":412},"Context",[395,450,451],{"class":408},")",[395,453,454],{"class":429}," error\n",[395,456,458],{"class":397,"line":457},5,[395,459,460],{"class":408},"}\n",[395,462,464],{"class":397,"line":463},6,[395,465,467],{"emptyLinePlaceholder":466},true,"\n",[395,469,471],{"class":397,"line":470},7,[395,472,473],{"class":401},"\u002F\u002F Params describes one process run.\n",[395,475,477,479,482,484],{"class":397,"line":476},8,[395,478,409],{"class":408},[395,480,481],{"class":412}," Params",[395,483,416],{"class":408},[395,485,419],{"class":408},[395,487,489,492,495],{"class":397,"line":488},9,[395,490,491],{"class":425},"    ListenAddr      ",[395,493,494],{"class":429},"string",[395,496,497],{"class":401},"                          \u002F\u002F startup log only\n",[395,499,501,504,507,509,512],{"class":397,"line":500},10,[395,502,503],{"class":425},"    ShutdownTimeout ",[395,505,506],{"class":412},"time",[395,508,445],{"class":408},[395,510,511],{"class":412},"Duration",[395,513,514],{"class":401},"                   \u002F\u002F budget for the in-flight drain\n",[395,516,518,521,524,527],{"class":397,"line":517},11,[395,519,520],{"class":425},"    Serve           ",[395,522,523],{"class":408},"func()",[395,525,526],{"class":429}," error",[395,528,529],{"class":401},"                    \u002F\u002F blocking listen; runs in a goroutine\n",[395,531,533,536,538,540,542,544,546,548],{"class":397,"line":532},12,[395,534,535],{"class":425},"    Shutdown        ",[395,537,439],{"class":408},[395,539,442],{"class":412},[395,541,445],{"class":408},[395,543,448],{"class":412},[395,545,451],{"class":408},[395,547,526],{"class":429},[395,549,550],{"class":401},"     \u002F\u002F stop accepting, drain in-flight\n",[395,552,554,557,560,563],{"class":397,"line":553},13,[395,555,556],{"class":425},"    Closers         ",[395,558,559],{"class":408},"[]",[395,561,562],{"class":412},"Closer",[395,564,565],{"class":401},"                        \u002F\u002F ordered cleanup list\n",[395,567,569,572,574,576,578,580,582,584],{"class":397,"line":568},14,[395,570,571],{"class":425},"    Flush           ",[395,573,439],{"class":408},[395,575,442],{"class":412},[395,577,445],{"class":408},[395,579,448],{"class":412},[395,581,451],{"class":408},[395,583,526],{"class":429},[395,585,586],{"class":401},"     \u002F\u002F telemetry flush, very last\n",[395,588,590],{"class":397,"line":589},15,[395,591,460],{"class":408},[395,593,595],{"class":397,"line":594},16,[395,596,467],{"emptyLinePlaceholder":466},[395,598,600,603,607,610,614,617,619,621,624,627,629,631],{"class":397,"line":599},17,[395,601,602],{"class":408},"func",[395,604,606],{"class":605},"s2Zo4"," Run",[395,608,609],{"class":408},"(",[395,611,613],{"class":612},"sHdIc","ctx",[395,615,616],{"class":412}," context",[395,618,445],{"class":408},[395,620,448],{"class":412},[395,622,623],{"class":408},",",[395,625,626],{"class":612}," p",[395,628,481],{"class":412},[395,630,451],{"class":408},[395,632,454],{"class":429},[348,634,635,638],{},[357,636,637],{},"Run"," blocks until the process stops, driving the following machinery:",[640,641,642,665,681,705,723,735],"ol",{},[643,644,645,650,651,654,655,657,658,660,661,664],"li",{},[352,646,647],{},[357,648,649],{},"signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)",": ",[357,652,653],{},"SIGINT","\u002F",[357,656,359],{}," cancels the context. Since it derives from the parent ",[357,659,613],{},", ",[352,662,663],{},"cancelling the parent context also triggers shutdown",": in tests this is how you stop a running service without a signal.",[643,666,667,673,674,676,677,680],{},[352,668,669,672],{},[357,670,671],{},"Serve"," starts in a goroutine."," If it returns an error on its own (e.g. the port is taken), ",[357,675,637],{}," exits with an ",[357,678,679],{},"app: listen:"," error, but the closers and the flush still run.",[643,682,683,650,686,688,689,696,697,700,701,704],{},[352,684,685],{},"On signal",[357,687,637],{}," restores default signal handling (which is why ",[352,690,691,692,695],{},"a second ",[357,693,694],{},"Ctrl-C"," kills the process immediately, no mercy","), then calls ",[357,698,699],{},"Shutdown"," with a ",[357,702,703],{},"ShutdownTimeout","-bounded context. This is where in-flight requests drain.",[643,706,707,710,711,715,716,718,719,722],{},[352,708,709],{},"Cleanup with its own budget",": the closers and the flush get a ",[712,713,714],"em",{},"fresh"," context, also ",[357,717,703],{}," long. Even if the HTTP drain consumed the whole budget, cleanup never starts with zero time: closing the pool and exporting telemetry don't fall victim to one slow request. (Both shutdown contexts detach from the parent via ",[357,720,721],{},"context.WithoutCancel",": an already-cancelled parent cannot cut the orderly shutdown short.)",[643,724,725,728,729,445],{},[352,726,727],{},"Closers run in LIFO order",", then ",[352,730,731,734],{},[357,732,733],{},"Flush"," runs very last",[643,736,737,738,741,742,745],{},"A clean shutdown returns ",[357,739,740],{},"nil","; partial failures accumulate via ",[357,743,744],{},"errors.Join",", so one failing closer doesn't swallow the others'.",[747,748,750],"h3",{"id":749},"why-lifo-and-why-is-the-flush-last","Why LIFO, and why is the flush last?",[348,752,753,754,757,758,761],{},"Closers run in ",[352,755,756],{},"reverse registration order"," because that closes dependencies in the right direction: what you opened first (say, the pgx pool) may still be needed by what you built on it later (say, a consumer that uses it). What you opened last, you close first, exactly how ",[357,759,760],{},"defer"," works within a function.",[348,763,764,765,768],{},"The telemetry flush is last because ",[352,766,767],{},"closers themselves emit telemetry",": the log line of a pool shutdown, a span opened during cleanup only reaches the OTLP endpoint or Sentry if the exporters are still alive. If the flush ran before the closers, the last seconds of shutdown would be a blind spot.",[375,770,772],{"id":771},"who-sits-on-it","Who sits on it?",[348,774,775,776,779,780,783],{},"All three chassis fill in the same ",[357,777,778],{},"app.Run",", only the contents of ",[357,781,782],{},"Params"," differ:",[785,786,787,811],"table",{},[788,789,790],"thead",{},[791,792,793,796,801,806],"tr",{},[794,795,782],"th",{},[794,797,798],{},[357,799,800],{},"fiberx.Run",[794,802,803],{},[357,804,805],{},"chix.Run",[794,807,808],{},[357,809,810],{},"worker.Run",[812,813,814,834,853,875],"tbody",{},[791,815,816,821,826,831],{},[817,818,819],"td",{},[357,820,671],{},[817,822,823],{},[357,824,825],{},"fiberApp.Listen(...)",[817,827,828],{},[357,829,830],{},"srv.ListenAndServe",[817,832,833],{},"asynq server + outbox relay + scheduler",[791,835,836,840,845,850],{},[817,837,838],{},[357,839,699],{},[817,841,842],{},[357,843,844],{},"fiberApp.ShutdownWithContext",[817,846,847],{},[357,848,849],{},"srv.Shutdown",[817,851,852],{},"stop the loops, drain in-flight tasks",[791,854,855,860,867,871],{},[817,856,857],{},[357,858,859],{},"Closers",[817,861,862,863,866],{},"the ",[357,864,865],{},"WithCloser"," options",[817,868,862,869,866],{},[357,870,865],{},[817,872,862,873,866],{},[357,874,865],{},[791,876,877,881,887,890],{},[817,878,879],{},[357,880,733],{},[817,882,883,884],{},"the shutdown function from ",[357,885,886],{},"telemetry.Setup",[817,888,889],{},"same",[817,891,889],{},[348,893,894,895,897,898,900,901,904,905,909],{},"All three start with ",[357,896,886],{}," and pass the returned shutdown function as ",[357,899,733],{},", so the \"Sentry and OTel flush at the very end\" guarantee is identical regardless of engine or binary type. In the worker, the in-flight drain means something different: tasks not finished within ",[357,902,903],{},"WORKER_SHUTDOWN_TIMEOUT"," are requeued by asynq and redelivered later (",[906,907,908],"a",{"href":193},"at-least-once",").",[348,911,912,913,915],{},"If you were to build your own fourth chassis (say, a gRPC server), you would put it on ",[357,914,778],{}," the same way: the package has no HTTP dependency.",[375,917,919],{"id":918},"the-shops-maingo","The shop's main.go",[348,921,922,923,926,927,930],{},"In practice you touch the lifecycle at exactly one point: you register a closer for everything you opened in ",[357,924,925],{},"main",". In the ",[906,928,929],{"href":41},"shop sample app","'s entry point that is the pgx pool:",[932,933,934,1104],"code-group",{},[386,935,938],{"className":388,"code":936,"filename":937,"language":390,"meta":391,"style":391},"pool := pg.MustNewPool(ctx, cfg.DB)\n\nerr := fiberx.Run(ctx, cfg.Server, func(app *fiber.App) error {\n    \u002F\u002F ... module registration ...\n    return nil\n}, fiberx.WithCloser(\"pgxpool\", func(context.Context) error {\n    pool.Close()\n    return nil\n}))\n","cmd\u002Fshop\u002Fmain.go (Fiber)",[357,939,940,973,977,1028,1033,1042,1080,1093,1099],{"__ignoreMap":391},[395,941,942,945,948,951,953,956,958,960,962,965,967,970],{"class":397,"line":398},[395,943,944],{"class":425},"pool ",[395,946,947],{"class":408},":=",[395,949,950],{"class":425}," pg",[395,952,445],{"class":408},[395,954,955],{"class":605},"MustNewPool",[395,957,609],{"class":408},[395,959,613],{"class":425},[395,961,623],{"class":408},[395,963,964],{"class":425}," cfg",[395,966,445],{"class":408},[395,968,969],{"class":425},"DB",[395,971,972],{"class":408},")\n",[395,974,975],{"class":397,"line":405},[395,976,467],{"emptyLinePlaceholder":466},[395,978,979,982,984,987,989,991,993,995,997,999,1001,1004,1006,1009,1011,1014,1017,1019,1022,1024,1026],{"class":397,"line":422},[395,980,981],{"class":425},"err ",[395,983,947],{"class":408},[395,985,986],{"class":425}," fiberx",[395,988,445],{"class":408},[395,990,637],{"class":605},[395,992,609],{"class":408},[395,994,613],{"class":425},[395,996,623],{"class":408},[395,998,964],{"class":425},[395,1000,445],{"class":408},[395,1002,1003],{"class":425},"Server",[395,1005,623],{"class":408},[395,1007,1008],{"class":408}," func(",[395,1010,372],{"class":612},[395,1012,1013],{"class":408}," *",[395,1015,1016],{"class":412},"fiber",[395,1018,445],{"class":408},[395,1020,1021],{"class":412},"App",[395,1023,451],{"class":408},[395,1025,526],{"class":429},[395,1027,419],{"class":408},[395,1029,1030],{"class":397,"line":433},[395,1031,1032],{"class":401},"    \u002F\u002F ... module registration ...\n",[395,1034,1035,1039],{"class":397,"line":457},[395,1036,1038],{"class":1037},"s7zQu","    return",[395,1040,1041],{"class":408}," nil\n",[395,1043,1044,1047,1049,1051,1053,1055,1058,1062,1064,1066,1068,1070,1072,1074,1076,1078],{"class":397,"line":463},[395,1045,1046],{"class":408},"},",[395,1048,986],{"class":425},[395,1050,445],{"class":408},[395,1052,865],{"class":605},[395,1054,609],{"class":408},[395,1056,1057],{"class":408},"\"",[395,1059,1061],{"class":1060},"sfazB","pgxpool",[395,1063,1057],{"class":408},[395,1065,623],{"class":408},[395,1067,1008],{"class":408},[395,1069,442],{"class":412},[395,1071,445],{"class":408},[395,1073,448],{"class":412},[395,1075,451],{"class":408},[395,1077,526],{"class":429},[395,1079,419],{"class":408},[395,1081,1082,1085,1087,1090],{"class":397,"line":470},[395,1083,1084],{"class":425},"    pool",[395,1086,445],{"class":408},[395,1088,1089],{"class":605},"Close",[395,1091,1092],{"class":408},"()\n",[395,1094,1095,1097],{"class":397,"line":476},[395,1096,1038],{"class":1037},[395,1098,1041],{"class":408},[395,1100,1101],{"class":397,"line":488},[395,1102,1103],{"class":408},"}))\n",[386,1105,1108],{"className":388,"code":1106,"filename":1107,"language":390,"meta":391,"style":391},"pool := pg.MustNewPool(ctx, cfg.DB)\n\nerr := chix.Run(ctx, cfg.Server, func(r chi.Router) error {\n    \u002F\u002F ... module registration ...\n    return nil\n}, chix.WithCloser(\"pgxpool\", func(context.Context) error {\n    pool.Close()\n    return nil\n}))\n","cmd\u002Fshop\u002Fmain.go (chi)",[357,1109,1110,1136,1140,1186,1190,1196,1230,1240,1246],{"__ignoreMap":391},[395,1111,1112,1114,1116,1118,1120,1122,1124,1126,1128,1130,1132,1134],{"class":397,"line":398},[395,1113,944],{"class":425},[395,1115,947],{"class":408},[395,1117,950],{"class":425},[395,1119,445],{"class":408},[395,1121,955],{"class":605},[395,1123,609],{"class":408},[395,1125,613],{"class":425},[395,1127,623],{"class":408},[395,1129,964],{"class":425},[395,1131,445],{"class":408},[395,1133,969],{"class":425},[395,1135,972],{"class":408},[395,1137,1138],{"class":397,"line":405},[395,1139,467],{"emptyLinePlaceholder":466},[395,1141,1142,1144,1146,1149,1151,1153,1155,1157,1159,1161,1163,1165,1167,1169,1172,1175,1177,1180,1182,1184],{"class":397,"line":422},[395,1143,981],{"class":425},[395,1145,947],{"class":408},[395,1147,1148],{"class":425}," chix",[395,1150,445],{"class":408},[395,1152,637],{"class":605},[395,1154,609],{"class":408},[395,1156,613],{"class":425},[395,1158,623],{"class":408},[395,1160,964],{"class":425},[395,1162,445],{"class":408},[395,1164,1003],{"class":425},[395,1166,623],{"class":408},[395,1168,1008],{"class":408},[395,1170,1171],{"class":612},"r",[395,1173,1174],{"class":412}," chi",[395,1176,445],{"class":408},[395,1178,1179],{"class":412},"Router",[395,1181,451],{"class":408},[395,1183,526],{"class":429},[395,1185,419],{"class":408},[395,1187,1188],{"class":397,"line":433},[395,1189,1032],{"class":401},[395,1191,1192,1194],{"class":397,"line":457},[395,1193,1038],{"class":1037},[395,1195,1041],{"class":408},[395,1197,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1228],{"class":397,"line":463},[395,1199,1046],{"class":408},[395,1201,1148],{"class":425},[395,1203,445],{"class":408},[395,1205,865],{"class":605},[395,1207,609],{"class":408},[395,1209,1057],{"class":408},[395,1211,1061],{"class":1060},[395,1213,1057],{"class":408},[395,1215,623],{"class":408},[395,1217,1008],{"class":408},[395,1219,442],{"class":412},[395,1221,445],{"class":408},[395,1223,448],{"class":412},[395,1225,451],{"class":408},[395,1227,526],{"class":429},[395,1229,419],{"class":408},[395,1231,1232,1234,1236,1238],{"class":397,"line":470},[395,1233,1084],{"class":425},[395,1235,445],{"class":408},[395,1237,1089],{"class":605},[395,1239,1092],{"class":408},[395,1241,1242,1244],{"class":397,"line":476},[395,1243,1038],{"class":1037},[395,1245,1041],{"class":408},[395,1247,1248],{"class":397,"line":488},[395,1249,1103],{"class":408},[348,1251,1252,1253,1255,1256,1259,1260,1263,1264,1266,1267,1269,1270,1273,1274,445],{},"So when a ",[357,1254,359],{}," arrives, the order is: the shop stops accepting requests → the in-progress ",[357,1257,1258],{},"PlaceOrder"," still completes (with its transaction and its ",[906,1261,1262],{"href":183},"outbox write",") → the ",[357,1265,1061],{}," closer closes the pool → telemetry flushes the request's spans. Under Kubernetes this is exactly the behavior a rolling deploy expects: tune ",[357,1268,703],{}," (",[357,1271,1272],{},"SHUTDOWN_TIMEOUT",", default 10s) to be shorter than the pod's ",[357,1275,1276],{},"terminationGracePeriodSeconds",[1278,1279,1280,1281,1284,1285,1287,1288,1291],"note",{},"The closer's name (",[357,1282,1283],{},"\"pgxpool\"",") is not decoration: when a closer fails, ",[357,1286,637],{}," returns the error as ",[357,1289,1290],{},"app: closer \"pgxpool\": ...",", so the log immediately shows which cleanup broke.",[375,1293,1295],{"id":1294},"what-this-means-day-to-day","What this means day to day",[1297,1298,1299,1313,1325],"ul",{},[643,1300,1301,1304,1305,1307,1308,1307,1310,1312],{},[352,1302,1303],{},"You don't write signal handling."," Ever. Calling ",[357,1306,800],{}," \u002F ",[357,1309,805],{},[357,1311,810],{}," is the lifecycle.",[643,1314,1315,1321,1322,1324],{},[352,1316,1317,1318,1320],{},"Resources opened in ",[357,1319,925],{}," get a closer",": the generators wire the pgx pool's for you; your own (say, a Redis client) you add with ",[357,1323,865],{},", in opening order.",[643,1326,1327,1333],{},[352,1328,1329,1330,1332],{},"The double ",[357,1331,694],{}," escape hatch stays",": if a drain ever hangs during development, the second signal kills instantly.",[348,1335,1336,1337,1339,1340,1342],{},"The HTTP-side sequence (telemetry → app construction → registration → listen → shutdown) is walked through on the ",[906,1338,68],{"href":69}," page; the worker-side drain details on the ",[906,1341,192],{"href":193}," page.",[1344,1345,1346],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":391,"searchDepth":405,"depth":405,"links":1348},[1349,1352,1353,1354],{"id":377,"depth":405,"text":378,"children":1350},[1351],{"id":749,"depth":422,"text":750},{"id":771,"depth":405,"text":772},{"id":918,"depth":405,"text":919},{"id":1294,"depth":405,"text":1295},"The app package: the signal-driven graceful shutdown every gpsystem binary sits on.","md",null,{},{"icon":76},{"title":73,"description":1355},"Ucs4mIcBXvMH8VFZfIeF0ggDzPbT7p9JZ44uiZHU7nY",[1363,1365],{"title":68,"path":69,"stem":70,"description":1364,"icon":71,"children":-1},"The kit's package map, the lifecycle of a service, and the principle that holds it together.",{"title":78,"path":79,"stem":80,"description":1366,"icon":81,"children":-1},"RFC 9457 problem details everywhere, with errs errors carrying stacks, codes and client-safe messages underneath.",1784668706301]