@lensmcp/cluster 1.24.5 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/executors/gateway/runtime/proxy.js +1 -1
- package/gc-notice.d.ts +13 -0
- package/gc-notice.js +1 -0
- package/main.devserver.js +10 -10
- package/package.json +4 -4
- package/pod-memory.d.ts +2 -0
- package/pod-memory.js +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ee=Object.defineProperty;var O=(t,d)=>ee(t,"name",{value:d,configurable:!0});var Z=Object.defineProperty,s=O((t,d)=>Z(t,"name",{value:d,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.wedgedMessage=wedgedMessage,exports.createProxy=createProxy;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),edge_1=require("./edge"),gateway_errors_1=require("../gateway-errors"),body_stall_1=require("../body-stall"),types_1=require("./types"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys");function isConnError(t){const d=t.code??"";return d==="ECONNREFUSED"||d==="ECONNRESET"||d==="ETIMEDOUT"||d==="EPIPE"||d==="EHOSTUNREACH"||d==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(t.message)}O(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(t){if(!IDEMPOTENT_METHODS.has((t.method??"GET").toUpperCase())||t.headers["transfer-encoding"]!==void 0)return!1;const d=t.headers["content-length"];return!(d!==void 0&&String(d).trim()!=="0")}O(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(t,d){return d.headersSent||d.writableEnded||d.destroyed===!0||t.aborted===!0}O(edgeGone,"edgeGone"),s(edgeGone,"edgeGone");const HOP_BY_HOP=new Set(["connection","keep-alive","proxy-connection","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),UPSTREAM_CONNECTION_HEADERS=["connection","keep-alive","proxy-connection"];function returnRequestId(t,d){if(t["x-request-id"]!==void 0)return;const f=d.headers["x-request-id"],R=Array.isArray(f)?f[0]:f;typeof R=="string"&&R!==""&&(t["x-request-id"]=R)}O(returnRequestId,"returnRequestId"),s(returnRequestId,"returnRequestId");const httpProxy=require("http-proxy");function declaredFirstByteMs(t){const d=Number(t);return Number.isFinite(d)&&d>0?d:types_1.POD_RESPONSE_TIMEOUT_MS}O(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");const secs=s(t=>`${(t/1e3).toFixed(1)}s`,"secs");function wedgedMessage(t,d,f){const R=f.failure==="gone"?"its process is gone":f.failure==="silent"?`it did not answer its liveness check for ${secs(f.silentMs)}`:"it gave no liveness answer";return`Pod ${t} of ${d} sent no response for ${secs(f.heldMs)} and ${R} \u2014 taken out of rotation, and the service is being restarted, so work it was still doing for this request may have been cut short. Retry if the call is safe to repeat.`}O(wedgedMessage,"wedgedMessage"),s(wedgedMessage,"wedgedMessage");function armFirstByteGuard(t){let d,f=!1,R=!1,U,D=0,$=(0,body_stall_1.requestBodyArriving)(t.req)?void 0:Date.now();const q=s(()=>{$??=Date.now()},"onBodyEnd");$===void 0&&t.req.once("end",q);const L=s(()=>($===void 0&&!(0,body_stall_1.requestBodyArriving)(t.req)&&($=Date.now()),$===void 0?0:Date.now()-$),"heldMs"),I=s(k=>{d=setTimeout(V,k),d.unref?.()},"arm"),V=s(()=>{if(f||!t.pending())return;const k=L();if(t.ceilingMs<=0){if($!==void 0&&k>=t.firstByteMs){t.onWedged(`no response within ${k}ms (wedged socket) \u2014 evicted + respawning`,{heldMs:k,silentMs:0,probes:0,failure:"deadline"});return}I($===void 0?t.firstByteMs:t.firstByteMs-k);return}(0,pod_probe_1.probePodAliveVerdict)(t.socketPath,void 0,t.serviceKey).then(({alive:x,failure:C})=>{if(f||!t.pending())return;const T=L();if(!x){if(D+=1,C==="silent"&&types_1.POD_WEDGE_CONFIRM_MS>0&&(U??=Date.now(),Date.now()-U<types_1.POD_WEDGE_CONFIRM_MS)){I(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}const N=U===void 0?0:Date.now()-U,H=C==="gone"?"its socket no longer accepts (the pod process is gone)":C==="silent"?`nothing answered on ${types_1.POD_ALIVE_PATH} for ${N}ms (${D} probe${D===1?"":"s"})`:`no liveness answer on ${types_1.POD_ALIVE_PATH}`;t.onWedged(`no response within ${T}ms and ${H} (wedged socket) \u2014 evicted + respawning`,{heldMs:T,silentMs:N,probes:D,failure:C??"no-route"});return}if(U=void 0,T>=t.ceilingMs){t.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${T}ms (ceiling ${t.ceilingMs}ms) \u2014 giving up on this request, pod kept`,x);return}!R&&$!==void 0&&(R=!0,t.onSlow(T,x)),I(Math.max(t.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return I(t.firstByteMs),{clear:s(()=>{f=!0,d&&clearTimeout(d),t.req.removeListener("end",q)},"clear")}}O(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(t,d,f){const{recordEdge:R,finishTrace:U,traceStep:D,emit:$}=d,q=s((e,r,o,c)=>{const p=c.kind==="pod-evicted";$(p?"error":"warning",`${p?"pod evicted":"request given up"}: ${e.project} (${o}) \u2014 ${c.status}`,`cluster-pod-incident:${e.project}`,{kind:c.kind,project:e.project,pod:o,status:c.status,reason:c.reason,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0],...c.detail??{}})},"reportPodIncident"),L=s(async(e,r)=>{const o=e.pool;if(!o)return;o.scannedAt=0;let c=(0,discovery_1.pickSock)(o,t.scanTtlMs);return!c&&e.svc&&(D(r,"cold-start",{project:e.project}),await f.ensureUp(e.svc)&&(o.scannedAt=0,c=(0,discovery_1.pickSock)(o,t.scanTtlMs))),c},"anotherPod"),I=s((e,r,o,c,p)=>{console.log(`[gateway] pod ${o} of ${e.project} is gone \u2014 its ${r.method} was not answered, sending it to ${c}`),$("info",`request moved to another pod: ${e.project} (${o} \u2192 ${c})`,`cluster-pod-moved:${e.project}`,{kind:"pod-gone-rerouted",project:e.project,pod:o,to:c,reason:p,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0]})},"reportPodMoved"),V=s(async e=>{if(!e.lens||!t.lensSupervisor)return"unsupported";let r;const o=new Promise(g=>{r=setTimeout(()=>g("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),c=t.lensSupervisor.recover(e.lens,"proxy-connection").then(g=>g?"ready":"failed").catch(()=>"failed"),p=await Promise.race([c,o]);r&&clearTimeout(r);const S=t.lensSupervisor.status(e.lens);return p==="failed"&&(!S||S.state==="ready")?"unsupported":p},"waitForManagedRecovery"),k=s((e,r,o,c,p,S)=>{if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}const g=e.lens?t.lensSupervisor?.status(e.lens):void 0,_=S==="timeout"||g?.state==="starting"||g?.state==="replacing",P=(0,gateway_errors_1.ensureRequestId)(r),l=e.lens?{project:e.project,reason:g?.reason??"connection-failure",generation:g?.generation??0,..._?{retryAfterMs:2e3}:{}}:void 0,a=_?503:502,y=_?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${P} project=${e.project} target=${String(c)} state=${g?.state??"unmanaged"} generation=${g?.generation??0}: ${p.message}`),$("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:P,project:e.project,target:String(c),state:g?.state??"unmanaged",generation:g?.generation,error:p.message}),(0,edge_1.sendError)(o,r,a,y,_?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,l,_?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),x=(0,body_stall_1.createBodyStallGuard)(t.bodyStallMs??types_1.BODY_STALL_MS),C={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},T=new http.Agent(C),N=new(require("node:https")).Agent(C),H=s(e=>typeof e=="string"&&e.startsWith("https:")?N:T,"agentForTarget"),B=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:T}),Y=s((e,r,o)=>{const c=s(()=>{o()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",c),e.once("close",()=>r.removeListener("close",c))},"propagateEdgeCancel");B.on("proxyReq",(e,r,o)=>{for(const c of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(c);e.setHeader("connection","keep-alive"),Y(e,o,()=>o.writableEnded)}),B.on("proxyRes",(e,r,o)=>{const c=r.__lensmcpPod;c&&(e.headers["x-lensmcp-pod"]=c),delete e.headers["x-powered-by"],e.headers.server="LensMCP",returnRequestId(e.headers,r),o&&Y(e,o,()=>o.writableEnded),o&&x.armResponse(o)});const Q=s((e,r,o,c)=>{(0,edge_1.stampForwardingHeaders)(t,r),x.arm(r);const p={},S=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[n,i]of Object.entries(r.headers))i===void 0||n.startsWith(":")||HOP_BY_HOP.has(n)||S&&(n==="content-length"||n==="transfer-encoding")||(p[n]=i);let g=http,_;if(typeof e=="object")_={socketPath:e.socketPath,path:r.url,method:r.method,headers:p,agent:T};else{const n=new URL(e),i=n.protocol==="https:";g=i?require("node:https"):http,p.host=n.host,_={protocol:n.protocol,hostname:n.hostname,port:n.port||(i?443:80),path:r.url,method:r.method,headers:p,agent:H(e)}}const P=o;P.__lensmcpErrContained||(P.__lensmcpErrContained=!0,o.on("error",()=>{}));let l;const a=s(()=>{o.writableEnded||u.destroy()},"onReqGone"),y=s(()=>{l&&!l.destroyed&&l.destroy()},"onEdgeClose"),m=s(()=>{r.removeListener("error",a),r.removeListener("aborted",a),r.removeListener("close",a),o.removeListener("close",y)},"detach"),u=g.request(_,n=>{if(l=n,c.onResponse(),o.headersSent||o.writableEnded||o.destroyed){n.destroy();return}const i={};for(const[b,A]of Object.entries(n.headers))A===void 0||HOP_BY_HOP.has(b)||(i[b]=A);delete i["x-powered-by"],i.server="LensMCP",returnRequestId(i,r);const E=r.__lensmcpPod;E&&(i["x-lensmcp-pod"]=E);try{o.writeHead(n.statusCode??502,i)}catch(b){console.error("[gateway] h2 relay: bad response header, resetting stream:",b.message),n.destroy(),o.destroyed||o.destroy(b);return}n.pipe(o),x.armResponse(o);const j=s(()=>{!o.writableEnded&&!o.destroyed&&o.destroy()},"abortEdge");n.once("error",j),n.once("close",j),o.on("close",y)});u.once("error",n=>c.onError(n)),u.once("close",m),r.on("error",a),r.on("aborted",a),r.on("close",a),S?u.end():r.pipe(u)},"pipeUpstream"),X=s(async(e,r,o)=>{const c=r.__lensmcpTrace;if(e.pool){const l=e.svc;l&&(l.lastUsed=Date.now(),l.inflight+=1,o.on("close",()=>{l.inflight-=1,l.lastUsed=Date.now()}));let a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!a&&l&&(D(c,"cold-start",{project:e.project}),await f.ensureUp(l)&&(a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),l&&a&&f.markUp(l,"observed"),l&&f.scaleUp(l),!a){const m=l?.lastErrors?.length?` Service errors: ${l.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${m} The watcher recovers and the next request retries automatically.`);return}const y=s((m,u)=>{const n=path.basename(m,".sock");r.__lensmcpPod=n,D(c,"pod-select",{pod:n,pods:e.pool.socks.length,strategy:"round-robin",...u?{movedFrom:u}:{}});let i=!1;const E=s((h,M="unlink")=>{if(e.pool.socks=e.pool.socks.filter(w=>w!==m),e.pool.idx=-1,M==="unlink")try{fs.unlinkSync(m)}catch{}console.error(`[gateway] pod ${n} of ${e.project}: ${h}`)},"evictPod"),j=armFirstByteGuard({socketPath:m,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!i&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((h,M)=>{i=!0,E(h),q(e,r,n,{kind:"pod-evicted",status:502,reason:h,detail:M}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(n,e.project,M))},"onWedged"),onCeiling:s(h=>{i=!0,E(h,"keep"),q(e,r,n,{kind:"request-gave-up",status:504,reason:h}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${n} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((h,M)=>{console.log(`[gateway] pod ${n} of ${e.project}: slow first byte, pod alive (${h}ms, ${M.inflight} in-flight, ${M.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>j.clear());const b=m,A=isReplayableRequest(r);let F=0;const W=s(()=>{F+=1,Q({socketPath:b},r,o,{onResponse:s(()=>{j.clear(),i=!0},"onResponse"),onError:s(h=>{if(!i){if(edgeGone(r,o)){j.clear(),i=!0;return}(0,pod_probe_1.podConnectionVerdict)(b,h).then(M=>{if(!i){if(M==="alive"&&F<2&&A&&!edgeGone(r,o)){console.log(`[gateway] pod ${n} of ${e.project}: ${h.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),W();return}if(j.clear(),i=!0,M==="gone"){E(h.message);const w=s(()=>{q(e,r,n,{kind:"pod-evicted",status:502,reason:`${h.message} \u2014 its socket no longer accepts (the pod process is gone)${u?`; the request had already been moved here from ${u}`:""}`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${n} of ${e.project} is gone (${h.message}) \u2014 retry.`)},"giveUp");if(u||!A){w();return}L(e,c).then(v=>{if(!edgeGone(r,o)){if(!v){w();return}I(e,r,n,path.basename(v,".sock"),h.message),y(v,n)}},w);return}console.error(`[gateway] pod ${n} of ${e.project}: ${h.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${n} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");W()},"onPod");y(a);return}const p=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let S=0,g=!1;const _=isReplayableRequest(r),P=s(()=>{if(edgeGone(r,o))return;const l=e.target;Q(l,r,o,{onResponse:s(()=>{},"onResponse"),onError:s(a=>{if(isConnError(a)&&(S+=1),e.lens&&isConnError(a)&&_&&S>=2&&!g&&!edgeGone(r,o)){g=!0,V(e).then(y=>{if(!edgeGone(r,o)){if(y==="ready"){P();return}if(y==="unsupported"&&Date.now()<p){setTimeout(P,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}k(e,r,o,e.target,a,y)}});return}if(isConnError(a)&&_&&Date.now()<p&&!edgeGone(r,o)){setTimeout(P,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}k(e,r,o,l,a)},"onError")})},"tryTcp");P()},"forwardNative");return{proxy:B,forward:s(async(e,r,o)=>{const c=Date.now(),p=r.__lensmcpTrace;if(o.on("close",()=>{R(e,Date.now()-c,o.statusCode??0,r.__lensmcpCaller),U(p,e.project,o.statusCode??0,{...r.__lensmcpPod?{pod:r.__lensmcpPod}:{},...r.__lensmcpCaller?{caller:r.__lensmcpCaller}:{}},e)}),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(t,r),x.arm(r),(r.httpVersionMajor??1)>=2){await X(e,r,o);return}if(e.pool){const a=e.svc;a&&(a.lastUsed=Date.now(),a.inflight+=1,o.on("close",()=>{a.inflight-=1,a.lastUsed=Date.now()}));let y=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!y&&a&&(D(p,"cold-start",{project:e.project}),await f.ensureUp(a)&&(y=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),a&&y&&f.markUp(a,"observed"),a&&f.scaleUp(a),!y){const u=a?.lastErrors?.length?` Service errors: ${a.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${u} The watcher recovers and the next request retries automatically.`);return}const m=s((u,n)=>{const i=path.basename(u,".sock");r.__lensmcpPod=i,D(p,"pod-select",{pod:i,pods:e.pool.socks.length,strategy:"round-robin",...n?{movedFrom:n}:{}});let E=!1;const j=s((w,v="unlink")=>{if(e.pool.socks=e.pool.socks.filter(G=>G!==u),e.pool.idx=-1,v==="unlink")try{fs.unlinkSync(u)}catch{}console.error(`[gateway] pod ${i} of ${e.project}: ${w}`)},"evictPod"),b=armFirstByteGuard({socketPath:u,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!E&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((w,v)=>{E=!0,j(w),q(e,r,i,{kind:"pod-evicted",status:502,reason:w,detail:v}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(i,e.project,v))},"onWedged"),onCeiling:s(w=>{E=!0,j(w,"keep"),q(e,r,i,{kind:"request-gave-up",status:504,reason:w}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${i} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((w,v)=>{console.log(`[gateway] pod ${i} of ${e.project}: slow first byte, pod alive (${w}ms, ${v.inflight} in-flight, ${v.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>b.clear());const A=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),F=u,W=isReplayableRequest(r);let h=0;const M=s(()=>{h+=1;const w=Date.now();B.web(r,o,{target:{socketPath:F},agent:T,proxyTimeout:A},v=>{if(!E){if(edgeGone(r,o)){b.clear(),E=!0;return}(0,pod_probe_1.podConnectionVerdict)(F,v).then(G=>{if(E)return;const J=Date.now()-w>=A-250;if(G==="alive"&&h<2&&W&&!J&&!edgeGone(r,o)){console.log(`[gateway] pod ${i} of ${e.project}: ${v.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),M();return}if(b.clear(),E=!0,G==="gone"){j(v.message);const K=s(()=>{q(e,r,i,{kind:"pod-evicted",status:502,reason:`${v.message} \u2014 its socket no longer accepts (the pod process is gone)${n?`; the request had already been moved here from ${n}`:""}`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} is gone (${v.message}) \u2014 retry.`)},"giveUp");if(n||!W||J){K();return}L(e,p).then(z=>{if(!edgeGone(r,o)){if(!z){K();return}I(e,r,i,path.basename(z,".sock"),v.message),m(z,i)}},K);return}console.error(`[gateway] pod ${i} of ${e.project}: ${v.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");M()},"onPod");m(y);return}const S=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let g=0,_=!1;const P=isReplayableRequest(r),l=s(()=>{if(edgeGone(r,o))return;const a=e.target,y={target:a,autoRewrite:typeof a=="string",changeOrigin:!0,agent:H(a)};B.web(r,o,y,m=>{if(isConnError(m)&&(g+=1),e.lens&&isConnError(m)&&P&&g>=2&&!_&&!edgeGone(r,o)){_=!0,V(e).then(u=>{if(!edgeGone(r,o)){if(u==="ready"){l();return}if(u==="unsupported"&&Date.now()<S){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}k(e,r,o,e.target,m,u)}});return}if(isConnError(m)&&P&&Date.now()<S&&!edgeGone(r,o)){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}o.destroyed||k(e,r,o,a,m)})},"tryTcp");l()},"forward"),agentForTarget:H,destroyAgents:s(()=>{x.stop(),T.destroy(),N.destroy()},"destroyAgents"),closeProxy:s(()=>{try{B.close?.()}catch{}},"closeProxy")}}O(createProxy,"createProxy"),s(createProxy,"createProxy");
|
|
1
|
+
"use strict";var ee=Object.defineProperty;var D=(t,d)=>ee(t,"name",{value:d,configurable:!0});var Z=Object.defineProperty,s=D((t,d)=>Z(t,"name",{value:d,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.wedgedMessage=wedgedMessage,exports.createProxy=createProxy;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),edge_1=require("./edge"),gateway_errors_1=require("../gateway-errors"),body_stall_1=require("../body-stall"),types_1=require("./types"),pod_probe_1=require("./pod-probe"),gc_notice_1=require("../../../gc-notice"),service_keys_1=require("./service-keys");function isConnError(t){const d=t.code??"";return d==="ECONNREFUSED"||d==="ECONNRESET"||d==="ETIMEDOUT"||d==="EPIPE"||d==="EHOSTUNREACH"||d==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(t.message)}D(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(t){if(!IDEMPOTENT_METHODS.has((t.method??"GET").toUpperCase())||t.headers["transfer-encoding"]!==void 0)return!1;const d=t.headers["content-length"];return!(d!==void 0&&String(d).trim()!=="0")}D(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(t,d){return d.headersSent||d.writableEnded||d.destroyed===!0||t.aborted===!0}D(edgeGone,"edgeGone"),s(edgeGone,"edgeGone");const HOP_BY_HOP=new Set(["connection","keep-alive","proxy-connection","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),UPSTREAM_CONNECTION_HEADERS=["connection","keep-alive","proxy-connection"];function returnRequestId(t,d){if(t["x-request-id"]!==void 0)return;const f=d.headers["x-request-id"],O=Array.isArray(f)?f[0]:f;typeof O=="string"&&O!==""&&(t["x-request-id"]=O)}D(returnRequestId,"returnRequestId"),s(returnRequestId,"returnRequestId");const httpProxy=require("http-proxy");function declaredFirstByteMs(t){const d=Number(t);return Number.isFinite(d)&&d>0?d:types_1.POD_RESPONSE_TIMEOUT_MS}D(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");const secs=s(t=>`${(t/1e3).toFixed(1)}s`,"secs");function wedgedMessage(t,d,f){const O=f.failure==="gone"?"its process is gone":f.failure==="silent"?`it did not answer its liveness check for ${secs(f.silentMs)}`:"it gave no liveness answer";return`Pod ${t} of ${d} sent no response for ${secs(f.heldMs)} and ${O} \u2014 taken out of rotation, and the service is being restarted, so work it was still doing for this request may have been cut short. Retry if the call is safe to repeat.`}D(wedgedMessage,"wedgedMessage"),s(wedgedMessage,"wedgedMessage");function armFirstByteGuard(t){let d,f=!1,O=!1,x,R=0,$=(0,body_stall_1.requestBodyArriving)(t.req)?void 0:Date.now();const q=s(()=>{$??=Date.now()},"onBodyEnd");$===void 0&&t.req.once("end",q);const L=s(()=>($===void 0&&!(0,body_stall_1.requestBodyArriving)(t.req)&&($=Date.now()),$===void 0?0:Date.now()-$),"heldMs"),U=s(b=>{d=setTimeout(V,b),d.unref?.()},"arm"),V=s(()=>{if(f||!t.pending())return;const b=L();if(t.ceilingMs<=0){if($!==void 0&&b>=t.firstByteMs){t.onWedged(`no response within ${b}ms (wedged socket) \u2014 evicted + respawning`,{heldMs:b,silentMs:0,probes:0,failure:"deadline"});return}U($===void 0?t.firstByteMs:t.firstByteMs-b);return}(0,pod_probe_1.probePodAliveVerdict)(t.socketPath,void 0,t.serviceKey).then(({alive:A,failure:C})=>{if(f||!t.pending())return;const T=L();if(!A){if(R+=1,C==="silent"&&(0,gc_notice_1.inAnnouncedGc)(t.socketPath)){x=void 0,U(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}if(C==="silent"&&types_1.POD_WEDGE_CONFIRM_MS>0&&(x??=Date.now(),Date.now()-x<types_1.POD_WEDGE_CONFIRM_MS)){U(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}const N=x===void 0?0:Date.now()-x,H=C==="gone"?"its socket no longer accepts (the pod process is gone)":C==="silent"?`nothing answered on ${types_1.POD_ALIVE_PATH} for ${N}ms (${R} probe${R===1?"":"s"})`:`no liveness answer on ${types_1.POD_ALIVE_PATH}`;t.onWedged(`no response within ${T}ms and ${H} (wedged socket) \u2014 evicted + respawning`,{heldMs:T,silentMs:N,probes:R,failure:C??"no-route"});return}if(x=void 0,T>=t.ceilingMs){t.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${T}ms (ceiling ${t.ceilingMs}ms) \u2014 giving up on this request, pod kept`,A);return}!O&&$!==void 0&&(O=!0,t.onSlow(T,A)),U(Math.max(t.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return U(t.firstByteMs),{clear:s(()=>{f=!0,d&&clearTimeout(d),t.req.removeListener("end",q)},"clear")}}D(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(t,d,f){const{recordEdge:O,finishTrace:x,traceStep:R,emit:$}=d,q=s((e,r,o,c)=>{const p=c.kind==="pod-evicted";$(p?"error":"warning",`${p?"pod evicted":"request given up"}: ${e.project} (${o}) \u2014 ${c.status}`,`cluster-pod-incident:${e.project}`,{kind:c.kind,project:e.project,pod:o,status:c.status,reason:c.reason,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0],...c.detail??{}})},"reportPodIncident"),L=s(async(e,r)=>{const o=e.pool;if(!o)return;o.scannedAt=0;let c=(0,discovery_1.pickSock)(o,t.scanTtlMs);return!c&&e.svc&&(R(r,"cold-start",{project:e.project}),await f.ensureUp(e.svc)&&(o.scannedAt=0,c=(0,discovery_1.pickSock)(o,t.scanTtlMs))),c},"anotherPod"),U=s((e,r,o,c,p)=>{console.log(`[gateway] pod ${o} of ${e.project} is gone \u2014 its ${r.method} was not answered, sending it to ${c}`),$("info",`request moved to another pod: ${e.project} (${o} \u2192 ${c})`,`cluster-pod-moved:${e.project}`,{kind:"pod-gone-rerouted",project:e.project,pod:o,to:c,reason:p,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0]})},"reportPodMoved"),V=s(async e=>{if(!e.lens||!t.lensSupervisor)return"unsupported";let r;const o=new Promise(g=>{r=setTimeout(()=>g("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),c=t.lensSupervisor.recover(e.lens,"proxy-connection").then(g=>g?"ready":"failed").catch(()=>"failed"),p=await Promise.race([c,o]);r&&clearTimeout(r);const S=t.lensSupervisor.status(e.lens);return p==="failed"&&(!S||S.state==="ready")?"unsupported":p},"waitForManagedRecovery"),b=s((e,r,o,c,p,S)=>{if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}const g=e.lens?t.lensSupervisor?.status(e.lens):void 0,_=S==="timeout"||g?.state==="starting"||g?.state==="replacing",P=(0,gateway_errors_1.ensureRequestId)(r),l=e.lens?{project:e.project,reason:g?.reason??"connection-failure",generation:g?.generation??0,..._?{retryAfterMs:2e3}:{}}:void 0,a=_?503:502,y=_?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${P} project=${e.project} target=${String(c)} state=${g?.state??"unmanaged"} generation=${g?.generation??0}: ${p.message}`),$("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:P,project:e.project,target:String(c),state:g?.state??"unmanaged",generation:g?.generation,error:p.message}),(0,edge_1.sendError)(o,r,a,y,_?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,l,_?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),A=(0,body_stall_1.createBodyStallGuard)(t.bodyStallMs??types_1.BODY_STALL_MS),C={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},T=new http.Agent(C),N=new(require("node:https")).Agent(C),H=s(e=>typeof e=="string"&&e.startsWith("https:")?N:T,"agentForTarget"),B=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:T}),Y=s((e,r,o)=>{const c=s(()=>{o()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",c),e.once("close",()=>r.removeListener("close",c))},"propagateEdgeCancel");B.on("proxyReq",(e,r,o)=>{for(const c of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(c);e.setHeader("connection","keep-alive"),Y(e,o,()=>o.writableEnded)}),B.on("proxyRes",(e,r,o)=>{const c=r.__lensmcpPod;c&&(e.headers["x-lensmcp-pod"]=c),delete e.headers["x-powered-by"],e.headers.server="LensMCP",returnRequestId(e.headers,r),o&&Y(e,o,()=>o.writableEnded),o&&A.armResponse(o)});const Q=s((e,r,o,c)=>{(0,edge_1.stampForwardingHeaders)(t,r),A.arm(r);const p={},S=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[n,i]of Object.entries(r.headers))i===void 0||n.startsWith(":")||HOP_BY_HOP.has(n)||S&&(n==="content-length"||n==="transfer-encoding")||(p[n]=i);let g=http,_;if(typeof e=="object")_={socketPath:e.socketPath,path:r.url,method:r.method,headers:p,agent:T};else{const n=new URL(e),i=n.protocol==="https:";g=i?require("node:https"):http,p.host=n.host,_={protocol:n.protocol,hostname:n.hostname,port:n.port||(i?443:80),path:r.url,method:r.method,headers:p,agent:H(e)}}const P=o;P.__lensmcpErrContained||(P.__lensmcpErrContained=!0,o.on("error",()=>{}));let l;const a=s(()=>{o.writableEnded||u.destroy()},"onReqGone"),y=s(()=>{l&&!l.destroyed&&l.destroy()},"onEdgeClose"),v=s(()=>{r.removeListener("error",a),r.removeListener("aborted",a),r.removeListener("close",a),o.removeListener("close",y)},"detach"),u=g.request(_,n=>{if(l=n,c.onResponse(),o.headersSent||o.writableEnded||o.destroyed){n.destroy();return}const i={};for(const[k,I]of Object.entries(n.headers))I===void 0||HOP_BY_HOP.has(k)||(i[k]=I);delete i["x-powered-by"],i.server="LensMCP",returnRequestId(i,r);const w=r.__lensmcpPod;w&&(i["x-lensmcp-pod"]=w);try{o.writeHead(n.statusCode??502,i)}catch(k){console.error("[gateway] h2 relay: bad response header, resetting stream:",k.message),n.destroy(),o.destroyed||o.destroy(k);return}n.pipe(o),A.armResponse(o);const j=s(()=>{!o.writableEnded&&!o.destroyed&&o.destroy()},"abortEdge");n.once("error",j),n.once("close",j),o.on("close",y)});u.once("error",n=>c.onError(n)),u.once("close",v),r.on("error",a),r.on("aborted",a),r.on("close",a),S?u.end():r.pipe(u)},"pipeUpstream"),X=s(async(e,r,o)=>{const c=r.__lensmcpTrace;if(e.pool){const l=e.svc;l&&(l.lastUsed=Date.now(),l.inflight+=1,o.on("close",()=>{l.inflight-=1,l.lastUsed=Date.now()}));let a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!a&&l&&(R(c,"cold-start",{project:e.project}),await f.ensureUp(l)&&(a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),l&&a&&f.markUp(l,"observed"),l&&f.scaleUp(l),!a){const v=l?.lastErrors?.length?` Service errors: ${l.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${v} The watcher recovers and the next request retries automatically.`);return}const y=s((v,u)=>{const n=path.basename(v,".sock");r.__lensmcpPod=n,R(c,"pod-select",{pod:n,pods:e.pool.socks.length,strategy:"round-robin",...u?{movedFrom:u}:{}});let i=!1;const w=s((h,M="unlink")=>{if(e.pool.socks=e.pool.socks.filter(E=>E!==v),e.pool.idx=-1,M==="unlink")try{fs.unlinkSync(v)}catch{}console.error(`[gateway] pod ${n} of ${e.project}: ${h}`)},"evictPod"),j=armFirstByteGuard({socketPath:v,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!i&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((h,M)=>{i=!0,w(h),q(e,r,n,{kind:"pod-evicted",status:502,reason:h,detail:M}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(n,e.project,M))},"onWedged"),onCeiling:s(h=>{i=!0,w(h,"keep"),q(e,r,n,{kind:"request-gave-up",status:504,reason:h}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${n} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((h,M)=>{console.log(`[gateway] pod ${n} of ${e.project}: slow first byte, pod alive (${h}ms, ${M.inflight} in-flight, ${M.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>j.clear());const k=v,I=isReplayableRequest(r);let F=0;const W=s(()=>{F+=1,Q({socketPath:k},r,o,{onResponse:s(()=>{j.clear(),i=!0},"onResponse"),onError:s(h=>{if(!i){if(edgeGone(r,o)){j.clear(),i=!0;return}(0,pod_probe_1.podConnectionVerdict)(k,h).then(M=>{if(!i){if(M==="alive"&&F<2&&I&&!edgeGone(r,o)){console.log(`[gateway] pod ${n} of ${e.project}: ${h.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),W();return}if(j.clear(),i=!0,M==="gone"){w(h.message);const E=s(()=>{q(e,r,n,{kind:"pod-evicted",status:502,reason:`${h.message} \u2014 its socket no longer accepts (the pod process is gone)${u?`; the request had already been moved here from ${u}`:""}`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${n} of ${e.project} is gone (${h.message}) \u2014 retry.`)},"giveUp");if(u||!I){E();return}L(e,c).then(m=>{if(!edgeGone(r,o)){if(!m){E();return}U(e,r,n,path.basename(m,".sock"),h.message),y(m,n)}},E);return}console.error(`[gateway] pod ${n} of ${e.project}: ${h.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${n} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");W()},"onPod");y(a);return}const p=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let S=0,g=!1;const _=isReplayableRequest(r),P=s(()=>{if(edgeGone(r,o))return;const l=e.target;Q(l,r,o,{onResponse:s(()=>{},"onResponse"),onError:s(a=>{if(isConnError(a)&&(S+=1),e.lens&&isConnError(a)&&_&&S>=2&&!g&&!edgeGone(r,o)){g=!0,V(e).then(y=>{if(!edgeGone(r,o)){if(y==="ready"){P();return}if(y==="unsupported"&&Date.now()<p){setTimeout(P,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}b(e,r,o,e.target,a,y)}});return}if(isConnError(a)&&_&&Date.now()<p&&!edgeGone(r,o)){setTimeout(P,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}b(e,r,o,l,a)},"onError")})},"tryTcp");P()},"forwardNative");return{proxy:B,forward:s(async(e,r,o)=>{const c=Date.now(),p=r.__lensmcpTrace;if(o.on("close",()=>{O(e,Date.now()-c,o.statusCode??0,r.__lensmcpCaller),x(p,e.project,o.statusCode??0,{...r.__lensmcpPod?{pod:r.__lensmcpPod}:{},...r.__lensmcpCaller?{caller:r.__lensmcpCaller}:{}},e)}),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(t,r),A.arm(r),(r.httpVersionMajor??1)>=2){await X(e,r,o);return}if(e.pool){const a=e.svc;a&&(a.lastUsed=Date.now(),a.inflight+=1,o.on("close",()=>{a.inflight-=1,a.lastUsed=Date.now()}));let y=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!y&&a&&(R(p,"cold-start",{project:e.project}),await f.ensureUp(a)&&(y=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),a&&y&&f.markUp(a,"observed"),a&&f.scaleUp(a),!y){const u=a?.lastErrors?.length?` Service errors: ${a.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${u} The watcher recovers and the next request retries automatically.`);return}const v=s((u,n)=>{const i=path.basename(u,".sock");r.__lensmcpPod=i,R(p,"pod-select",{pod:i,pods:e.pool.socks.length,strategy:"round-robin",...n?{movedFrom:n}:{}});let w=!1;const j=s((E,m="unlink")=>{if(e.pool.socks=e.pool.socks.filter(G=>G!==u),e.pool.idx=-1,m==="unlink")try{fs.unlinkSync(u)}catch{}console.error(`[gateway] pod ${i} of ${e.project}: ${E}`)},"evictPod"),k=armFirstByteGuard({socketPath:u,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!w&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((E,m)=>{w=!0,j(E),q(e,r,i,{kind:"pod-evicted",status:502,reason:E,detail:m}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(i,e.project,m))},"onWedged"),onCeiling:s(E=>{w=!0,j(E,"keep"),q(e,r,i,{kind:"request-gave-up",status:504,reason:E}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${i} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((E,m)=>{console.log(`[gateway] pod ${i} of ${e.project}: slow first byte, pod alive (${E}ms, ${m.inflight} in-flight, ${m.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>k.clear());const I=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),F=u,W=isReplayableRequest(r);let h=0;const M=s(()=>{h+=1;const E=Date.now();B.web(r,o,{target:{socketPath:F},agent:T,proxyTimeout:I},m=>{if(!w){if(edgeGone(r,o)){k.clear(),w=!0;return}(0,pod_probe_1.podConnectionVerdict)(F,m).then(G=>{if(w)return;const J=Date.now()-E>=I-250;if(G==="alive"&&h<2&&W&&!J&&!edgeGone(r,o)){console.log(`[gateway] pod ${i} of ${e.project}: ${m.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),M();return}if(k.clear(),w=!0,G==="gone"){j(m.message);const K=s(()=>{q(e,r,i,{kind:"pod-evicted",status:502,reason:`${m.message} \u2014 its socket no longer accepts (the pod process is gone)${n?`; the request had already been moved here from ${n}`:""}`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} is gone (${m.message}) \u2014 retry.`)},"giveUp");if(n||!W||J){K();return}L(e,p).then(z=>{if(!edgeGone(r,o)){if(!z){K();return}U(e,r,i,path.basename(z,".sock"),m.message),v(z,i)}},K);return}console.error(`[gateway] pod ${i} of ${e.project}: ${m.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");M()},"onPod");v(y);return}const S=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let g=0,_=!1;const P=isReplayableRequest(r),l=s(()=>{if(edgeGone(r,o))return;const a=e.target,y={target:a,autoRewrite:typeof a=="string",changeOrigin:!0,agent:H(a)};B.web(r,o,y,v=>{if(isConnError(v)&&(g+=1),e.lens&&isConnError(v)&&P&&g>=2&&!_&&!edgeGone(r,o)){_=!0,V(e).then(u=>{if(!edgeGone(r,o)){if(u==="ready"){l();return}if(u==="unsupported"&&Date.now()<S){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}b(e,r,o,e.target,v,u)}});return}if(isConnError(v)&&P&&Date.now()<S&&!edgeGone(r,o)){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}o.destroyed||b(e,r,o,a,v)})},"tryTcp");l()},"forward"),agentForTarget:H,destroyAgents:s(()=>{A.stop(),T.destroy(),N.destroy()},"destroyAgents"),closeProxy:s(()=>{try{B.close?.()}catch{}},"closeProxy")}}D(createProxy,"createProxy"),s(createProxy,"createProxy");
|
package/gc-notice.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** MUST NOT match a pod socket name (pod-sock.ts): the gateway's directory scan skips everything else. */
|
|
2
|
+
export declare const GC_NOTICE_SUFFIX = ".gc";
|
|
3
|
+
export declare function gcNoticePath(sockPath: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* How long a notice is believed. Longer than any full GC a pod should ever take (the 11.9 s above), short enough that
|
|
6
|
+
* a pod which died mid-collection, or never came out of one, is evicted within a minute. `LENSMCP_POD_GC_NOTICE_MAX_MS`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function gcNoticeMaxMs(env?: NodeJS.ProcessEnv): number;
|
|
9
|
+
/** Pod: run `gc` with the notice up. Returns what `gc` returns. */
|
|
10
|
+
export declare function withGcNotice<T>(sockPath: string | undefined, gc: () => T, now?: () => number): T;
|
|
11
|
+
export declare function clearGcNotice(sockPath: string): void;
|
|
12
|
+
/** Gateway: is this pod inside a GC it announced, and recently enough to be believed? */
|
|
13
|
+
export declare function inAnnouncedGc(sockPath: string, now?: number, maxMs?: number): boolean;
|
package/gc-notice.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var u=Object.defineProperty;var c=(e,t)=>u(e,"name",{value:t,configurable:!0});var i=Object.defineProperty,r=c((e,t)=>i(e,"name",{value:t,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GC_NOTICE_SUFFIX=void 0,exports.gcNoticePath=gcNoticePath,exports.gcNoticeMaxMs=gcNoticeMaxMs,exports.withGcNotice=withGcNotice,exports.clearGcNotice=clearGcNotice,exports.inAnnouncedGc=inAnnouncedGc;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs"));exports.GC_NOTICE_SUFFIX=".gc";function gcNoticePath(e){return`${e}${exports.GC_NOTICE_SUFFIX}`}c(gcNoticePath,"gcNoticePath"),r(gcNoticePath,"gcNoticePath");function gcNoticeMaxMs(e=process.env){const t=Number(e.LENSMCP_POD_GC_NOTICE_MAX_MS);return e.LENSMCP_POD_GC_NOTICE_MAX_MS!==void 0&&e.LENSMCP_POD_GC_NOTICE_MAX_MS!==""&&Number.isFinite(t)&&t>=0?t:6e4}c(gcNoticeMaxMs,"gcNoticeMaxMs"),r(gcNoticeMaxMs,"gcNoticeMaxMs");function withGcNotice(e,t,o=Date.now){if(!e)return t();try{fs.writeFileSync(gcNoticePath(e),String(o()),{mode:384})}catch{}try{return t()}finally{clearGcNotice(e)}}c(withGcNotice,"withGcNotice"),r(withGcNotice,"withGcNotice");function clearGcNotice(e){try{fs.unlinkSync(gcNoticePath(e))}catch{}}c(clearGcNotice,"clearGcNotice"),r(clearGcNotice,"clearGcNotice");function inAnnouncedGc(e,t=Date.now(),o=gcNoticeMaxMs()){try{const n=Number(fs.readFileSync(gcNoticePath(e),"utf8").trim());return Number.isFinite(n)&&n>0&&t-n>=0&&t-n<o}catch{return!1}}c(inAnnouncedGc,"inAnnouncedGc"),r(inAnnouncedGc,"inAnnouncedGc");
|
package/main.devserver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var rt=Object.defineProperty;var
|
|
1
|
+
"use strict";var rt=Object.defineProperty;var _=(a,h)=>rt(a,"name",{value:h,configurable:!0});var et=Object.defineProperty,i=_((a,h)=>et(a,"name",{value:h,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.redactInspectorUrls=exports.inspectorPortOf=exports.DEBUG_FIXED_PORTS_ENV=exports.mayDriveThePool=exports.isOwnerOnlyTransport=exports.gatewayManaged=void 0,exports.tell=tell,exports.usesFixedInspectorPorts=usesFixedInspectorPorts,exports.inspectorPortFor=inspectorPortFor;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),node_child_process_1=require("node:child_process"),http=tslib_1.__importStar(require("node:http")),readline=tslib_1.__importStar(require("node:readline")),inspector=tslib_1.__importStar(require("node:inspector")),util=tslib_1.__importStar(require("node:util")),v8=tslib_1.__importStar(require("node:v8")),exit_reason_1=require("./exit-reason"),hot_swap_registries_1=require("./hot-swap-registries"),process_name_1=require("./process-name"),inspector_secret_1=require("./inspector-secret");Object.defineProperty(exports,"inspectorPortOf",{enumerable:!0,get:i(function(){return inspector_secret_1.inspectorPortOf},"get")}),Object.defineProperty(exports,"redactInspectorUrls",{enumerable:!0,get:i(function(){return inspector_secret_1.redactInspectorUrls},"get")});const devserver_transport_1=require("./devserver-transport");Object.defineProperty(exports,"gatewayManaged",{enumerable:!0,get:i(function(){return devserver_transport_1.gatewayManaged},"get")}),Object.defineProperty(exports,"isOwnerOnlyTransport",{enumerable:!0,get:i(function(){return devserver_transport_1.isOwnerOnlyTransport},"get")}),Object.defineProperty(exports,"mayDriveThePool",{enumerable:!0,get:i(function(){return devserver_transport_1.mayDriveThePool},"get")});const schema_first_instance_1=require("./schema-first-instance"),redis_disconnect_fix_1=require("./redis-disconnect-fix"),error_stack_release_1=require("./error-stack-release"),swap_recycle_1=require("./swap-recycle"),handoff_retire_1=require("./handoff-retire"),gc_notice_1=require("./gc-notice"),pod_sock_1=require("./pod-sock"),notice_generation_1=require("./notice-generation"),service_build_events_1=require("./service-build-events"),stdio_guard_1=require("./stdio-guard"),bundle_parent_1=require("./bundle-parent"),checkout_cache_1=require("./checkout-cache"),dev_devtool_1=require("./dev-devtool"),pod_memory_1=require("./pod-memory"),pod_memory_events_1=require("./pod-memory-events"),heap_snapshot_1=require("./heap-snapshot"),httpProxy=require("http-proxy"),{prettyFactory}=require("pino-pretty"),BUNDLE_PATH=process.env.BUNDLE_PATH?.trim()||"./main.js",SERVICE_NAME=process.env.SERVICE_NAME||"",SERVICE_PREFIX=process.env.SERVE_PREFIX?`/${process.env.SERVE_PREFIX}`:"",GATEWAY_MIDDLEWARE_PATH=process.env.GATEWAY_MIDDLEWARE||"",GATEWAY_CONFIG=(()=>{const a=process.env.GATEWAY_CONFIG;if(!a)return{};try{return JSON.parse(a)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const a=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof a=="function"?a:typeof a.default=="function"?a.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(a){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,a)}function applyGatewayMiddleware(a){if(gatewayMiddleware)try{gatewayMiddleware(a,GATEWAY_CONFIG)}catch(h){console.error("[gateway] Middleware error:",h)}}_(applyGatewayMiddleware,"applyGatewayMiddleware"),i(applyGatewayMiddleware,"applyGatewayMiddleware");const GATEWAY_ROUTES=(()=>{try{return JSON.parse(process.env.GATEWAY_ROUTES||"[]")}catch{return[]}})(),GATEWAY_HTTPS=process.env.GATEWAY_HTTPS==="1";function hostMatches(a,h){if(a.startsWith("*.")){const g=a.slice(1);return h.endsWith(g)||h===a.slice(2)}return h===a}_(hostMatches,"hostMatches"),i(hostMatches,"hostMatches");function matchGatewayRoute(a){if(GATEWAY_ROUTES.length===0)return;const h=(a.headers.host||"").split(":")[0],g=a.url||"/";for(const S of GATEWAY_ROUTES)if(!(S.host&&!hostMatches(S.host,h))&&!(S.prefix&&!g.startsWith(S.prefix)))return S}_(matchGatewayRoute,"matchGatewayRoute"),i(matchGatewayRoute,"matchGatewayRoute");const WORKER_NAMES=(()=>{try{return JSON.parse(process.env.WORKERS||"[]")}catch{return[]}})(),LENSMCP_WS_KEY=process.env.LENSMCP_WS_KEY||"",SOCK_NS=process.env.LENSMCP_SOCK_NS||LENSMCP_WS_KEY,SOCK_DIR=SOCK_NS?path.join(os.tmpdir(),SOCK_NS,`${SERVICE_NAME||"lensmcp-cluster"}-devserver`):path.join(os.tmpdir(),`${SERVICE_NAME||"lensmcp-cluster"}-devserver`),SOCKET_ONLY=process.env.LENSMCP_SOCKET_ONLY==="1",POD_GENERATION=(()=>{const a=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(a)&&a>1?Math.floor(a):1})();function childSockPath(a){return path.join(SOCK_DIR,(0,pod_sock_1.podSockName)(a,POD_GENERATION))}_(childSockPath,"childSockPath"),i(childSockPath,"childSockPath");function sockOwnerPath(a){return`${a}.owner`}_(sockOwnerPath,"sockOwnerPath"),i(sockOwnerPath,"sockOwnerPath");function sockRetiringPath(a){return`${a}.retiring`}_(sockRetiringPath,"sockRetiringPath"),i(sockRetiringPath,"sockRetiringPath");function tell(a,h){if(!a.connected||typeof a.send!="function")return!1;try{return a.send(h,()=>{})}catch{return!1}}_(tell,"tell"),i(tell,"tell");function refusePoolCommand(a,h){console.warn(`[devserver] refused ${a.method} ${a.url} on the public port \u2014 under a gateway the pool is driven on the control socket only.`),h.writeHead(404,{"content-type":"application/json"}),h.end(JSON.stringify({error:"not found"}))}_(refusePoolCommand,"refusePoolCommand"),i(refusePoolCommand,"refusePoolCommand"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(a=process.env){const h=(a[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return h==="1"||h==="true"||h==="yes"||h==="on"}_(usesFixedInspectorPorts,"usesFixedInspectorPorts"),i(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(a,h,g=process.env){if(!usesFixedInspectorPorts(g))return 0;const S=Math.trunc(a)+Math.trunc(h);return Number.isInteger(S)&&S>1024&&S<=65535?S:0}_(inspectorPortFor,"inspectorPortFor"),i(inspectorPortFor,"inspectorPortFor");function ensureOwnerOnlyDir(a){try{fs.mkdirSync(a,{recursive:!0,mode:448})}catch{}try{const h=fs.lstatSync(a);h.isDirectory()&&(h.mode&63)!==0&&fs.chmodSync(a,448)}catch{}}_(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),i(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(a,h=process.pid){ensureOwnerOnlyDir(path.dirname(a));try{const g=sockOwnerPath(a);try{fs.unlinkSync(g)}catch{}fs.writeFileSync(g,String(h),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(a)}catch{}try{fs.unlinkSync(sockRetiringPath(a))}catch{}}_(claimSock,"claimSock"),i(claimSock,"claimSock");function sockOwnedByAnother(a,h=process.pid){try{const g=fs.readFileSync(sockOwnerPath(a),"utf8").trim();return!!g&&g!==String(h)}catch{return!1}}_(sockOwnedByAnother,"sockOwnedByAnother"),i(sockOwnedByAnother,"sockOwnedByAnother");function cleanupSock(a,h=process.pid){if(a){try{const g=fs.readFileSync(sockOwnerPath(a),"utf8").trim();if(g&&g!==String(h))return}catch{}try{fs.unlinkSync(a)}catch{}try{fs.unlinkSync(sockOwnerPath(a))}catch{}try{fs.unlinkSync(sockRetiringPath(a))}catch{}(0,gc_notice_1.clearGcNotice)(a)}}_(cleanupSock,"cleanupSock"),i(cleanupSock,"cleanupSock");function postRetiringNotice(a,h){if(!(!a||!h)){try{const g=fs.readFileSync(sockOwnerPath(a),"utf8").trim();if(g&&g!==String(h))return}catch{}try{fs.writeFileSync(sockRetiringPath(a),String(h),{flag:"w",mode:384})}catch{}}}_(postRetiringNotice,"postRetiringNotice"),i(postRetiringNotice,"postRetiringNotice");function serviceKeyFingerprint(){const a=process.env.LENSMCP_SERVICE_KEY;if(!a)return;const{createHash:h}=require("node:crypto");return h("sha256").update(a).digest("hex").slice(0,8)}_(serviceKeyFingerprint,"serviceKeyFingerprint"),i(serviceKeyFingerprint,"serviceKeyFingerprint");function rootModuleOf(a){let h;for(const g of a.getModules?.()?.values()??[])typeof g?.metatype!="function"||typeof g.distance!="number"||!Number.isFinite(g.distance)||(!h||g.distance<h.distance)&&(h=g);return h?.metatype}_(rootModuleOf,"rootModuleOf"),i(rootModuleOf,"rootModuleOf");let lastImportedModule;function takeImportedModule(){const a=lastImportedModule;return lastImportedModule=void 0,a}_(takeImportedModule,"takeImportedModule"),i(takeImportedModule,"takeImportedModule");async function importFresh(spec){process.env.DEVSERVER_MODE="1";const requested=path.isAbsolute(spec)?spec:path.join(__dirname,spec);let resolved=requested;try{resolved=fs.realpathSync(requested)}catch{}eval(`(() => {
|
|
2
2
|
const key = ${JSON.stringify(resolved)};
|
|
3
3
|
const previous = require.cache[key];
|
|
4
4
|
delete require.cache[key]; // delete from CJS cache
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
const at = owner && Array.isArray(owner.children) ? owner.children.indexOf(previous) : -1;
|
|
10
10
|
if (at >= 0) owner.children.splice(at, 1);
|
|
11
11
|
}
|
|
12
|
-
})()`),(0,bundle_parent_1.routeBundleRequires)(requested,resolved);const exports=eval(`require(${JSON.stringify(resolved)})`);try{const mod=eval("require.cache")[resolved];lastImportedModule=mod&&typeof mod=="object"?mod:void 0}catch{}return exports}f(importFresh,"importFresh"),i(importFresh,"importFresh");function debounce(c,_){let g=null;return(...S)=>{g&&clearTimeout(g),g=setTimeout(()=>c(...S),_)}}if(f(debounce,"debounce"),i(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const c=i(g=>{if(!inspector.url())try{inspector.open(g,"127.0.0.1",!1)}catch(F){console.warn(`[child] inspector.open(${g}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,F.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const S=inspector.url();S&&tell(process,{type:"inspector-url",url:S})},"openInspector"),_=process.env.CHILD_DEBUG_PORT;if(_!==void 0&&_!==""){const g=Number(_);Number.isInteger(g)&&(g===0||g>1024)&&c(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,S=!1,F,re=!1;const Le=(0,hot_swap_registries_1.registryPruningEnabled)(),ce=i((t,n)=>{const l=(0,hot_swap_registries_1.describePrune)(t,n);l&&console.log(l)},"logPrune");let U={app:null,handler:i((t,n)=>{n.statusCode=503,n.end("starting")},"handler"),inflight:0};const le=(()=>{const t=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(t)&&t>=0?t:15*6e4})(),T=i(t=>{const n=globalThis.__lensmcpActiveWork;if(typeof n!="function")return{count:0};try{const l=n(t);return{count:Number(l?.count??0),...l?.oldestStartedAt?{oldestStartedAt:l.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),ve=i(t=>{const n=globalThis.__lensmcpStopWorkIntake;if(typeof n=="function")try{return n(t)}catch{return}},"stopWorkIntake"),de=i(t=>{const n=globalThis.__lensmcpWorkIntakeReport;if(!(t===void 0||typeof n!="function"))try{return n(t)??void 0}catch{return}},"workIntakeReport"),ye=(()=>{const t=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(t)&&t>=1?Math.floor(t):1})(),x=new Set,Q=(()=>{const t=Number(process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS);return process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==void 0&&process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==""&&Number.isFinite(t)&&t>=0?t:3e4})(),j=i(t=>{if(!t.abandoned?.size)return 0;const n=Date.now();for(const[l,p]of t.abandoned)(l.writableEnded||n-p>=Q)&&t.abandoned.delete(l);return t.abandoned.size},"abandonedRunning"),oe=i(t=>{let n=0;for(const l of t.open??[])l.headersSent||(n+=1);return{unanswered:n+j(t),streams:Math.max(0,t.inflight-n),work:T(t.workGen).count}},"owedBy"),ne=i((t,n,l=`${n}s`)=>`${t} ${t===1?n:l}`,"plural");async function $e(){if(Q<=0)return;const t=Date.now();let n=!1;for(;!(x.size<ye);){const l=x.values().next().value;if(!l||l.closed)break;const p=oe(l);if(p.unanswered===0&&p.work===0)break;const m=(l.drainingSince??t)+Q-Date.now();if(m<=0)break;n||(n=!0,console.log(`[child] swap held \u2014 the app replaced ${Math.round((Date.now()-(l.drainingSince??t))/100)/10}s ago is still `+[p.unanswered?`answering ${ne(p.unanswered,"request")}`:"",p.work?`running ${ne(p.work,"background job")}`:""].filter(Boolean).join(" and ")+`, and this pod keeps at most ${ye} replaced app(s) alive (LENSMCP_SWAP_MAX_DRAINING). The new build is applied when it is done \u2014 in ${Math.ceil(m/1e3)}s at the latest (LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS).`)),await new Promise(E=>{setTimeout(E,Math.min(100,m)).unref?.()})}n&&console.log(`[child] swap resumed after ${Date.now()-t}ms`)}f($e,"xe"),i($e,"holdForOwedWork");const R=(0,swap_recycle_1.swapRecyclePolicy)();let pe,se=0;const We=(()=>{const t=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(t)&&t>=0?t:3500})();let h=!1,b=!1;const v=(0,pod_memory_1.podMemoryPolicy)();let q=0;const O=new Set;let ue=!1;const H=i(()=>{if(!ue)return;let t=0;for(const n of O)n.deref()===void 0?O.delete(n):t+=1;return t},"liveContainers"),k=(0,pod_memory_1.createPodMemoryMonitor)({policy:v,state:i(()=>({gen:U.gen??0,swaps:se,...pe===void 0?{}:{baseline:pe},busy:S||re||$.size>0,lastBusyAt:Y}),"state"),send:i(t=>{tell(process,t)},"send"),containers:H,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:i(()=>we(),"onRetained")}),G=i(()=>((0,pod_memory_1.fullGc)()&&k.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),L=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,n=`${path.sep}node_modules${path.sep}`,l=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:i(p=>l.has(p)||p.startsWith(t)&&!p.slice(t.length).includes(n)&&!p.slice(t.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let me=!1;function be(){if(!(!L||me))try{const t=L();t.errors>0&&console.log(`[child] read the stack of ${t.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(t){console.error("[child] could not release library error frames:",t),me=!0}}f(be,"Ne"),i(be,"releaseLibraryErrorFrames");const $=new Map;let Y=Date.now();const he=i(()=>R.settleMs<=0||!S&&!re&&$.size===0&&Date.now()-Y>=R.settleMs,"settled");let K;function ie(t){if(!(!R.enabled||h||b)){if(R.settleMs<=0){Z(t);return}K&&clearTimeout(K),K=setTimeout(()=>{K=void 0,he()?Z(t):ie(t)},R.settleMs),K.unref?.()}}f(ie,"de"),i(ie,"recycleCheckWhenSettled");const Me=i(()=>{if(R.stuckCloseMs<=0)return 0;const t=Date.now();let n=0;for(const l of $.values())t-l>=R.stuckCloseMs&&(n+=1);return n},"stuckCloses");function Z(t){if(!R.enabled||h||b||!process.send)return;const n=v8.getHeapStatistics().heap_size_limit,l=i(m=>{const E=k.oldestRetained();return{swaps:se,usedBytes:m,limitBytes:n,stuckCloses:Me(),settled:he(),...pe===void 0?{}:{baselineBytes:pe},...E?{retained:{generation:E.gen,ageMs:Date.now()-E.closedAt,gcs:E.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(R,l(process.memoryUsage().heapUsed)))return;const p=(0,swap_recycle_1.recycleReason)(R,l(G()));p&&(h=!0,console.log(`[child] asking for a fresh pod ${t} \u2014 ${p}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB / _RETAINED_MS, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),tell(process,{type:"recycle-request",reason:p}))}f(Z,"re"),i(Z,"considerRecycle");let fe;function we(){if(!R.enabled||R.retainedMs<=0||h||b)return;const t=k.oldestRetained();if(!t)return;const n=t.closedAt+R.retainedMs-Date.now();if(n<=0){Z("with a closed generation still in memory");return}fe||(fe=setTimeout(()=>{fe=void 0,k.measure("retention-check").catch(()=>{})},n),fe.unref?.())}f(we,"Oe"),i(we,"retainedRecycleCheck");function _e(t,n){const l={};n&&(l.module=n),t&&(l.app=t);try{const p=t?.app;if(p&&typeof p=="object"&&(l.nestApp=p,p.container&&typeof p.container=="object")){l.container=p.container;const m=rootModuleOf(p.container);m&&(l.rootModule=m)}}catch{}return l}f(_e,"we"),i(_e,"generationTargets");async function V(t,n){if(t.closed||(t.closed=!0,x.delete(t),n&&console.error(`[child] ${n}`),!t.app))return;$.set(t,Date.now());const l=R.enabled&&R.stuckCloseMs>0?setTimeout(()=>{$.has(t)&&(console.error(`[child] a previous app has been closing for ${Math.round(R.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),Z("with a previous app stuck in close()"))},R.stuckCloseMs):void 0;l?.unref?.();const p=t.app;try{await t.app.close()}catch(m){console.error("[child] error closing a previous app:",m)}finally{$.delete(t),Y=Date.now(),l&&clearTimeout(l),t.app=null,t.abandoned=void 0,t.handler=(m,E)=>{E.statusCode=503,E.end("closed")},t.gen!==void 0&&k.closed(t.gen,se,_e(p,t.module)),t.module=void 0}if(t.superseded){try{ce((0,hot_swap_registries_1.pruneRegistries)(t.superseded,"serve"),"after closing the previous app")}catch(m){console.error("[child] could not release the previous app's class registrations:",m)}t.superseded=void 0}ie("after closing the previous app")}f(V,"oe"),i(V,"closeGeneration");async function ke(t){const n=Date.now();t.drainingSince=n;const l=i(()=>({inflight:t.inflight+j(t),work:T(t.workGen).count}),"busy"),p=l();if(p.inflight>0||p.work>0){for(;x.size>=ye;){const I=x.values().next().value;if(!I)break;const P=oe(I),te=Math.round((Date.now()-(I.drainingSince??n))/1e3);if(P.unanswered===0&&P.streams===0&&P.work===0){await V(I);continue}await V(I,`closing a still-draining generation early \u2014 ${x.size} were pending and the cap is ${ye} (each holds a whole app: DI container, DB pool, queue workers). `+(P.unanswered>0||P.work>0?`It was replaced ${te}s ago and had still not answered ${ne(P.unanswered,"request")}`+(P.work?` or finished ${ne(P.work,"background job")}`:"")+` \u2014 those fail now (a queue job keeps its lock until the TTL lapses). Raise LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS (currently ${Q}ms) to wait longer for them. `:`It held only ${ne(P.streams,"open stream")}; their clients reconnect to the new app. `)+"Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.")}x.add(t)}for(;le>0&&Date.now()-n<le;){if(t.closed)return;const I=l();if(I.inflight===0&&I.work===0)break;await new Promise(P=>{setTimeout(P,250).unref?.()})}if(t.closed)return;const m=l(),E=de(t.workGen);m.inflight>0||m.work>0?console.error(`[child] swap drain expired after ${Date.now()-n}ms \u2014 closing the previous app with ${m.inflight} in-flight request(s) and ${m.work} background job(s) still running. `+(E?`Previous app still processing ${E.running} queue job(s); ${E.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${le}ms) if this is legitimate work.`):(Date.now()-n>250||E?.runningAtStop||E?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-n}ms \u2014 closing it`+(E?` (it finished ${E.finishedAfterStop} queue job(s) after the swap; ${E.takenAfterStop} were taken after the swap)`:"")),await V(t)}f(ke,"Ge"),i(ke,"drainThenClose");async function J(){if(S)return re=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),g;S=!0,Y=Date.now(),await $e(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(A,X)=>{console.error(`[child] uncaughtException (${X}):`,A)}),process.on("unhandledRejection",A=>{const X=A instanceof Error?A.stack||A.message:String(A);X.includes("FronteggContextScope")||X.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",A)});let t;if(Le)try{t=(0,hot_swap_registries_1.captureRegistries)()}catch{t=void 0}const n=global.__lensmcpBeginWorkGeneration,l=typeof n=="function"?n():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(A){if(!(A instanceof SyntaxError))throw A;console.warn(`[child] bundle was unreadable mid-write (${A.message}) \u2014 retrying the swap once`),await new Promise(X=>setTimeout(X,400)),await importFresh(BUNDLE_PATH)}}catch(A){throw S=!1,Y=Date.now(),A}if(typeof global.createChildApp!="function")throw S=!1,Y=Date.now(),new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);let p;if(t)try{p=(0,hot_swap_registries_1.supersededSince)(t),ce((0,hot_swap_registries_1.pruneRegistries)(p,"build"),"at the swap")}catch(A){console.error("[child] could not release superseded class registrations:",A)}let m;try{m=await global.createChildApp()}catch(A){throw S=!1,Y=Date.now(),takeImportedModule(),A}const E=U;p?.size&&(E.app?E.superseded=p:ce((0,hot_swap_registries_1.pruneRegistries)(p,"serve"),"at the swap")),g=m;const I=takeImportedModule();U={app:m,handler:m.handler,inflight:0,gen:++q,...I?{module:I}:{},...l===void 0?{}:{workGen:l}};const P=_e(m,void 0).container;P&&(O.add(new WeakRef(P)),ue=!0);const te=E.app&&l!==void 0?ve(l):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(te?.workers?`; its ${te.workers} queue worker(s) stopped taking new jobs, previous app still processing ${te.runningAtStop} job(s)`:"")),be(),E.app&&(se+=1,ke(E).catch(A=>console.error("[child] background close error:",A)),setTimeout(()=>Z("30s after a hot swap"),3e4).unref?.(),k.swapped(E.gen,()=>E.closed===!0&&!$.has(E))),S=!1,Y=Date.now(),re?(re=!1,J()):m}f(J,"q"),i(J,"swapNow");const Fe=debounce(async()=>{try{console.log("[child] /webpack/reload"),await J()}catch(t){console.error("[child] reload failed:",t)}},250),B=http.createServer((t,n)=>{if(t.method==="POST"&&t.url==="/webpack/reload"){n.writeHead(200,{"content-type":"application/json"}),n.end(JSON.stringify({ok:!0})),Fe();return}if(t.method==="GET"&&t.url==="/__lensmcp/alive"){const p=t.headers["x-internal-token"],m=process.env.LENSMCP_SERVICE_KEY;if(m&&p!==m){n.writeHead(404,{"content-type":"text/plain"}),n.end("not found");return}const E=T();n.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),n.end(JSON.stringify({ok:!0,pid:process.pid,swapping:S,inflight:U.inflight,oldestInflightAt:U.oldestAt,activeWork:E.count,oldestWorkAt:E.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...F?{watcher:F}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const l=U;l.inflight+=1,(l.open??=new Set).add(n),l.oldestAt===void 0&&(l.oldestAt=Date.now()),n.on("close",()=>{l.inflight-=1,l.open?.delete(n),Q>0&&!l.closed&&!n.headersSent&&!n.writableEnded&&((l.abandoned??=new Map).set(n,Date.now()),l.abandoned.size>256&&j(l)),l.inflight===0&&(l.oldestAt=void 0),k.activity()}),l.handler(t,n)});B.keepAliveTimeout=0;const W=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(W),process.exit(0)});try{const t=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const n=(0,schema_first_instance_1.prewarmZodFor)(t);n?.created&&console.log(`[child] zod${n.version?` ${n.version}`:""}: built ${n.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const n=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(t);n?.patched&&console.log(`[child] ioredis${n.versions.length?` ${n.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await J(),pe=(await k.booted()).live,console.log(`[child] Using bundle: ${t}`),claimSock(W),B.listen(W,()=>{console.log(`[child] Listening on ${W}`),tell(process,{type:"ready",socketPath:W})})}catch(t){console.error("[child] Startup error:",t),tell(process,{type:"boot-error",error:String(t?.stack||t)}),process.exitCode=1;return}process.on("message",async t=>{if(!(!t||typeof t!="object")){if(t.type==="debug-open"){const n=Number(t.port);c(Number.isInteger(n)&&n>1024?n:0);return}if(t.type==="watcher"){const n=t.testimony;n&&typeof n=="object"&&(F=n);return}if(t.type==="heap-snapshot"){ee(t);return}if(t.type==="recycle-declined"){h=!1;return}if(t.type==="retire"){ge();return}if(t.type==="reload"){if(b)return;try{const n=Date.now();await J(),tell(process,{type:"reloaded",socketPath:W,swap:{generation:q,swaps:se,swapMs:Date.now()-n,draining:x.size}})}catch(n){console.error("[child] reload error:",n),tell(process,{type:"reload-error",error:String(n?.stack||n)})}}}});let Ee=!1;async function ee(t){const n=i(l=>{tell(process,{type:"heap-snapshot-result",id:t.id,...l})},"reply");if(Ee)return n({ok:!1,refused:"a snapshot of this pod is already being written"});Ee=!0;try{const l=G(),p=k.tracker.views().map(P=>P.gen),m=(0,heap_snapshot_1.heapSnapshotPolicy)();t.maxHeapBytes&&(m.maxHeapBytes=t.maxHeapBytes);const E=(0,heap_snapshot_1.heapSnapshotRefusal)(m,{live:l,...t.available?{available:t.available}:{}});if(E)return n({ok:!1,refused:E,live:l});console.log(`[child] writing a heap snapshot (${Math.round(l/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const I=await(0,heap_snapshot_1.writeHeapSnapshot)(t.file);n({ok:!0,file:t.file,bytes:I.bytes,ms:I.ms,live:l,gen:U.gen??0,closedAlive:p})}catch(l){n({ok:!1,error:l?.message??String(l)})}finally{Ee=!1}}f(ee,"ne"),i(ee,"takeHeapSnapshot");async function ge(t="a fresh pod has taken over"){if(b)return;b=!0,k.stop(),console.log(`[child] retiring \u2014 ${t}; finishing in-flight work, then exiting`);const n=ve(Number.POSITIVE_INFINITY);n?.workers&&console.log(`[child] its ${n.workers} queue worker(s) stopped taking new jobs, still processing ${n.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath(W),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(p=>{setTimeout(p,We).unref?.()}),cleanupSock(W),B.close();const l=Date.now();for(;Date.now()-l<le;){B.closeIdleConnections?.();let p=0;for(const m of x)p+=oe(m).unanswered;if(U.inflight+j(U)===0&&p===0&&T().count===0)break;await new Promise(m=>{setTimeout(m,250).unref?.()})}setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(p){console.error("[child] close error while retiring:",p)}process.exit(0)}f(ge,"_e"),i(ge,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const Ce=i(async t=>{console.log(`[child ${t}] shutting down\u2026`);const n=g?.close().catch(p=>console.error("[child] close error:",p)),l=new Promise(p=>B.close(()=>p()));cleanupSock(W),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([n,l]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(t=>{process.on(t,()=>{if(t==="SIGTERM"&&!b&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){ge("this generation was handed over to a newer one");return}inspector?.close?.(),Ce(t)})})})()}else{let c=f(function(h){j.push(h),j.length>Q&&j.splice(0,j.length-Q),ye?.write((0,inspector_secret_1.redactInspectorUrls)(h.replace(x,"")));for(const b of oe)try{const v=b.plain?h.replace(x,""):h;b.res.write(b.redact?(0,inspector_secret_1.redactInspectorUrls)(v):v)}catch{oe.delete(b)}},"pe");i(c,"emitLogLine");const _=12,g=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",S=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),F=i(h=>({project:g,service:SERVICE_NAME||g,pod:h.id,pid:h.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),re=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],Le="\x1B[0m",ce=Math.max(1,Number(process.env.CHILD_COUNT||1)),U=LENSMCP_WS_KEY||path.basename(process.cwd()),le=i(h=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(U,SERVICE_NAME||"service",h)),"execPathFor");let T=(()=>{const h=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(h)&&h>1024?h:0})();const ve=20;T&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/closed/codex-security-06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${T}+slot, workers ${T}+${ve}+i). Any account on this host can connect to 127.0.0.1:${T} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const de=process.env.LENSMCP_LOG_FILE||"",ye=de?(()=>{try{fs.mkdirSync(path.dirname(de),{recursive:!0});const h=fs.createWriteStream(de,{flags:"a",mode:384});try{fs.chmodSync(de,384)}catch{}return h}catch(h){return console.warn(`[parent] cannot open log file ${de}:`,h.message),null}})():null,x=/\x1b\[[0-9;]*m/g,Q=2e3,j=[],oe=new Set,ne=(0,devserver_transport_1.gatewayManaged)();for(const h of["log","info","warn","error"]){const b=console[h].bind(console),v=h==="log"||h==="info"?process.stdout:process.stderr;console[h]=(...q)=>{b(...q.map(O=>typeof O=="string"?(0,inspector_secret_1.forStream)(v,ne,O):O)),c(q.map(O=>typeof O=="string"?O:util.inspect(O)).join(" ")+`
|
|
13
|
-
`)}}const $e=i(
|
|
14
|
-
`+String(H),"stack")}}),O=i((H,k)=>{const
|
|
15
|
-
`;G.write((0,inspector_secret_1.forStream)(G,ne,`${b}${L}`)),c(`${b}${L}`)},"write"),ue=i((H,k)=>{if(!H)return;const G=readline.createInterface({input:H});G.on("line",L=>O(k,L)),G.on("close",()=>{})},"attach");ue(h.stdout,"stdout"),ue(h.stderr,"stderr")},"wireProcLogging"),We=i(h=>se(h.proc,R(h.id),b=>h.tail.push(b)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const h=httpProxy.createProxyServer({});let b=Number(process.env.PORT)||0;b||(b=9090);const v=[],q=new Set;function O(){let e=0;for(;q.has(e);)e++;return q.add(e),e}f(O,"I"),i(O,"allocDebugSlot");let ue=1,H=0,k=!1,G=ce,L=[],me=0;const be=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),$=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),Y=i(e=>e.booting&&!$(e)&&Date.now()-e.spawnedAt<be,"stillBooting");function he(){return v.filter(e=>e.healthy&&typeof e.socketPath=="string")}f(he,"he"),i(he,"healthyChildren");let K,ie=0,Me;const Z={project:g,service:SERVICE_NAME||g,podGeneration:POD_GENERATION};function fe(e){const r=(0,service_build_events_1.buildAccountOf)(e.building);r&&r.startedAt!==ie&&(ie=r.startedAt,S.publish((0,service_build_events_1.buildServiceBuildEvent)(Z,"started",r,{root:process.cwd()})));const a=(0,service_build_events_1.buildAccountOf)(e.failed);a&&(ie=0,S.publish((0,service_build_events_1.buildServiceBuildEvent)(Z,"failed",a,{root:process.cwd()})))}f(fe,"fe"),i(fe,"recordBuildProgress");function we(e){if(!e||typeof e!="object")return;const r=e;if(r.schemaVersion===1){fe(r),"failed"in r&&delete r.failed,K=r;for(const a of v)tell(a.proc,{type:"watcher",testimony:r})}}f(we,"Oe"),i(we,"recordWatcherTestimony");function _e(e,r=256*1024){return new Promise(a=>{let u="",s=!1;e.on("data",o=>{s||(u+=o,u.length>r&&(s=!0))}),e.on("end",()=>{if(s||!u)return a(void 0);try{a(JSON.parse(u))}catch{a(void 0)}}),e.on("error",()=>a(void 0))})}f(_e,"we"),i(_e,"readJsonBody");function V(){const e=he();if(e.length===0)return null;const r=H%e.length;return H=(H+1)%e.length,e[r]}f(V,"oe"),i(V,"pickChild");function ke(e,r){const a=`Child#${e.id}`,u=SERVICE_NAME||g,s=Date.now();if(r.type==="pod-memory"){const o=r.report;if(!o||typeof o.live!="number")return;e.memory.latest=o,e.memory.history.push(o),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(a,o)}`),S.publish((0,pod_memory_events_1.buildPodMemoryEvent)(F(e),o))}else if(r.type==="pod-memory-retained"){const o=r.retained;if(!o||typeof o.gen!="number")return;e.memory.retained.set(o.gen,{...o,at:s}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(a,u,o)}`),S.publish((0,pod_memory_events_1.buildPodRetainedEvent)(F(e),o,s))}else{const o=r.released;if(!o||typeof o.gen!="number")return;e.memory.retained.delete(o.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(a,u,o)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(F(e),o,s))}}f(ke,"Ge"),i(ke,"recordPodMemory");const J=new Map;let Fe=0;async function B(){const e=ue++,r=childSockPath(e);cleanupSock(r);const a=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&a.unshift("--require","@swc-node/register"),a.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const u=T?O():-1,s=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:r,...u>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(T,u))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:a,execPath:le("pod")}),o={id:e,proc:s,healthy:!1,lastError:null,debugSlot:u,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:me,memory:{history:[],retained:new Map}};return s.on("error",w=>console.error(`[parent] Child#${e}: ${w.message}`)),s.on("message",w=>{if(!w||typeof w!="object")return;const d=w;if(d.type==="ready")o.socketPath=String(d.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),K&&tell(s,{type:"watcher",testimony:K}),me!==o.bootReloadSeq&&tell(s,{type:"reload"});else if(d.type==="recycle-request")l(o,String(d.reason||"it has outgrown its bound"));else if(d.type==="inspector-url")o.inspectorUrl=String(d.url),console.log(`[parent] Child#${o.id} Debugger listening on ${d.url}`);else if(d.type==="reloaded"){o.socketPath=String(d.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`);const y=d.swap;y&&typeof y.generation=="number"&&typeof y.swaps=="number"&&S.publish((0,service_build_events_1.buildServiceSwapEvent)(F(o),{generation:y.generation,swaps:y.swaps,swapMs:typeof y.swapMs=="number"?y.swapMs:0,draining:typeof y.draining=="number"?y.draining:0},Me,{root:process.cwd()}))}else d.type==="reload-error"?(o.lastError=String(d.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):d.type==="boot-error"?(o.lastError=String(d.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1):d.type==="pod-memory"||d.type==="pod-memory-retained"||d.type==="pod-memory-released"?ke(o,d):d.type==="heap-snapshot-result"&&J.get(Number(d.id))?.(d)}),s.on("exit",(w,d)=>{o.booting=!1;for(const M of o.memory.retained.values()){const C={gen:M.gen,closedAt:M.closedAt,ageMs:Date.now()-M.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${o.id}`,SERVICE_NAME||g,C)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(F(o),C,Date.now()))}o.memory.retained.clear(),o.memory.latest&&S.publish((0,pod_memory_events_1.buildPodExitedEvent)(F(o),{code:w,signal:d},Date.now())),o.debugSlot>=0&&(q.delete(o.debugSlot),o.debugSlot=-1);const y=d==="SIGINT"||d==="SIGTERM"||w===0;if(y?console.log(`[parent] Child#${o.id} exited cleanly (code=${w}, signal=${d??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${w}, signal=${d??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,w,d,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${w}, signal=${d??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!y&&!k){const M=v.indexOf(o);M>=0&&v.splice(M,1);const C=Date.now();if(L=L.filter(z=>C-z<3e4),L.length>=5){console.error(`[parent] crash-loop detected (${L.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}L.push(C),setTimeout(()=>{const z=v.filter(Te=>Te.proc.killed===!1).length;!k&&z<G&&(console.log(`[parent] respawning a crashed pod (${z}/${G} live)`),B().catch(()=>{}))},1500).unref?.()}}),v.push(o),We(o),o}f(B,"K"),i(B,"spawnChild");async function W(e){G=Math.max(G,e);const r=v.filter(u=>u.proc.killed===!1),a=e-r.length;for(let u=0;u<a;u++)await B()}f(W,"U"),i(W,"ensurePoolSize");async function Ee(){const e=v.filter(r=>(!r.healthy||typeof r.socketPath!="string")&&!Y(r));await Promise.all(e.map(async r=>{try{r.proc.kill("SIGTERM")}catch{}cleanupSock(r.socketPath,r.proc.pid),r.socketPath=void 0;const a=v.indexOf(r);a>=0&&v.splice(a,1),await B()}))}f(Ee,"Ee"),i(Ee,"respawnUnhealthy");const ee=new Set;let ge=!1,Ce=0;const t=3e4,n=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function l(e,r){const a=i(s=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${r}) \u2014 not now: ${s}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(k||!v.includes(e))return;if(ge)return a("another pod is already being replaced");const u=Date.now()-Ce;if(u<t)return a(`a pod was replaced ${Math.round(u/1e3)}s ago`);ge=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${r}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const s=await B(),o=Date.now()+be;for(;!s.healthy&&!s.lastError&&!$(s)&&Date.now()<o;)await new Promise(y=>{setTimeout(y,100).unref?.()});if(k)return;if(!s.healthy){const y=v.indexOf(s);y>=0&&v.splice(y,1);try{s.proc.kill("SIGTERM")}catch{}return a(`its successor Child#${s.id} did not come up (${s.lastError??"no answer in time"})`)}const w=v.indexOf(e);w>=0&&v.splice(w,1),e.healthy=!1,ee.add(e);const d=setTimeout(()=>{if(!$(e)){console.error(`[parent] Child#${e.id} was still running ${n}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},n);if(d.unref?.(),e.proc.once("exit",()=>{clearTimeout(d),ee.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}Ce=Date.now(),console.log(`[parent] Child#${s.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${s.proc.pid})`)}catch(s){a(`could not start a successor: ${s?.message??String(s)}`)}finally{ge=!1}}f(l,"c"),i(l,"recyclePod");const p=[];function m(e){const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),a=path.join(r,`${e}.js`);if(!fs.existsSync(a))return console.error(`[parent] Worker "${e}" bundle not found: ${a}`),null;const u=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&u.unshift("--require","@swc-node/register");try{u.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const s=inspectorPortFor(T||9229,ve+Math.max(0,WORKER_NAMES.indexOf(e)));u.push(T?`--inspect=127.0.0.1:${s}`:`--inspect-port=127.0.0.1:${s}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const o=(0,node_child_process_1.fork)(a,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:u,execPath:le(`w-${e}`)}),w={name:e,proc:o,debugPort:s};return p.push(w),o.on("error",d=>console.error(`[parent] Worker "${e}": ${d.message}`)),se(o,`\x1B[35m[worker:${e}] \x1B[0m`,d=>{const y=/Debugger listening on (wss?:\/\/\S+)/.exec(d);y&&(w.inspectorUrl=y[1])}),o.on("exit",(d,y)=>{y==="SIGINT"||y==="SIGTERM"||d===0||console.error(`[parent] Worker "${e}" crashed (code=${d}, signal=${y??"none"})`);const M=p.indexOf(w);M>=0&&p.splice(M,1)}),console.log(`[parent] Worker "${e}" started (pid=${o.pid})`),w}f(m,"y"),i(m,"spawnWorkerProcess");const E=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),I=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),P=new Set,te=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function A(e,r){if(P.delete(e),!te(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${r} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}f(A,"R"),i(A,"killDrainingWorker");function X(e){try{e.proc.kill("SIGTERM")}catch{}if(te(e))return;const r=[...P].filter(u=>u.name===e.name);for(;r.length>=I;)A(r.shift(),`was still running when a later reload replaced its successor (cap ${I})`);P.add(e);const a=setTimeout(()=>A(e,`was still running ${E}ms after SIGTERM`),E);a.unref?.(),e.proc.once("exit",()=>{clearTimeout(a),P.delete(e)})}f(X,"B"),i(X,"retireWorker");function qe(){for(const e of[...p])X(e);p.length=0;for(const e of WORKER_NAMES)m(e)}f(qe,"Ve"),i(qe,"restartWorkers");const Xe=debounce(async()=>{me+=1;const e=he();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const r of e)tell(r.proc,{type:"reload"})}await Ee(),WORKER_NAMES.length>0&&qe()},200),Be=i((e,r)=>{const a=(0,notice_generation_1.noticeGeneration)(e.headers);return a===void 0||a===POD_GENERATION?!1:(e.resume(),r.writeHead(409,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!1,generation:POD_GENERATION,sentFor:a})),e.url==="/webpack/reload"&&console.log(`[parent] a build of generation g${a} reached generation g${POD_GENERATION} \u2014 not reloaded: it is not this generation's build`),!0)},"refusedForAnotherGeneration"),Pe=i(async(e,r)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);if(Be(e,r))return;r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0})),_e(e).then(s=>{const o=s;Me=(0,service_build_events_1.buildAccountOf)(o?.build),ie=0,we(o?.watcher)}),Xe();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);if(Be(e,r))return;const s=await _e(e);we(s),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,relayed:K!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);let s="";e.on("data",o=>{s+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(s||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await W(o),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const s=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(s.get("tail")??"100")||0,0),Q),w=s.get("follow")!=="0",d=s.get("plain")==="1",y=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);r.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const M=o===0?"":j.slice(-o).join(""),C=d?M.replace(x,""):M;if(r.write(y?(0,inspector_secret_1.redactInspectorUrls)(C):C),!w){r.end();return}const z={res:r,plain:d,redact:y};oe.add(z),e.on("close",()=>oe.delete(z));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const s=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(s)&&s>1024&&(T=s),T||(T=9229);const o=v.filter(d=>d.proc.exitCode===null&&d.proc.signalCode===null);for(const d of o)d.debugSlot<0&&(d.debugSlot=O()),tell(d.proc,{type:"debug-open",port:inspectorPortFor(T,d.debugSlot)});for(const d of p)try{d.proc.kill("SIGUSR1")}catch{}const w=Date.now()+2e3;for(;Date.now()<w&&o.some(d=>!d.inspectorUrl);)await new Promise(d=>setTimeout(d,50));r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,basePort:T,fixedPorts:usesFixedInspectorPorts(),pods:o.map(d=>({id:d.id,inspectorUrl:d.inspectorUrl??null})),workers:p.map(d=>({name:d.name,port:d.debugPort||(0,inspector_secret_1.inspectorPortOf)(d.inspectorUrl),inspectorUrl:d.inspectorUrl??null}))},null,2)+`
|
|
16
|
-
`);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const s=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),o=[...v,...
|
|
17
|
-
`);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const s=i((
|
|
18
|
-
`)},"reply"),o=new URL(e.url,"http://localhost").searchParams,w=Number(o.get("pod")||""),d=o.has("maxMb")?Number(o.get("maxMb")):void 0;if(d!==void 0&&!(Number.isFinite(d)&&d>0)){s(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const y=Number.isInteger(w)&&w>0?v.find(
|
|
12
|
+
})()`),(0,bundle_parent_1.routeBundleRequires)(requested,resolved);const exports=eval(`require(${JSON.stringify(resolved)})`);try{const mod=eval("require.cache")[resolved];lastImportedModule=mod&&typeof mod=="object"?mod:void 0}catch{}return exports}_(importFresh,"importFresh"),i(importFresh,"importFresh");function debounce(a,h){let g=null;return(...S)=>{g&&clearTimeout(g),g=setTimeout(()=>a(...S),h)}}if(_(debounce,"debounce"),i(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const a=i(g=>{if(!inspector.url())try{inspector.open(g,"127.0.0.1",!1)}catch(F){console.warn(`[child] inspector.open(${g}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,F.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const S=inspector.url();S&&tell(process,{type:"inspector-url",url:S})},"openInspector"),h=process.env.CHILD_DEBUG_PORT;if(h!==void 0&&h!==""){const g=Number(h);Number.isInteger(g)&&(g===0||g>1024)&&a(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,S=!1,F,re=!1;const Le=(0,hot_swap_registries_1.registryPruningEnabled)(),ce=i((t,n)=>{const l=(0,hot_swap_registries_1.describePrune)(t,n);l&&console.log(l)},"logPrune");let U={app:null,handler:i((t,n)=>{n.statusCode=503,n.end("starting")},"handler"),inflight:0};const le=(()=>{const t=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(t)&&t>=0?t:15*6e4})(),T=i(t=>{const n=globalThis.__lensmcpActiveWork;if(typeof n!="function")return{count:0};try{const l=n(t);return{count:Number(l?.count??0),...l?.oldestStartedAt?{oldestStartedAt:l.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),ve=i(t=>{const n=globalThis.__lensmcpStopWorkIntake;if(typeof n=="function")try{return n(t)}catch{return}},"stopWorkIntake"),de=i(t=>{const n=globalThis.__lensmcpWorkIntakeReport;if(!(t===void 0||typeof n!="function"))try{return n(t)??void 0}catch{return}},"workIntakeReport"),ye=(()=>{const t=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(t)&&t>=1?Math.floor(t):1})(),G=new Set,Z=(()=>{const t=Number(process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS);return process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==void 0&&process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==""&&Number.isFinite(t)&&t>=0?t:3e4})(),j=i(t=>{if(!t.abandoned?.size)return 0;const n=Date.now();for(const[l,p]of t.abandoned)(l.writableEnded||n-p>=Z)&&t.abandoned.delete(l);return t.abandoned.size},"abandonedRunning"),oe=i(t=>{let n=0;for(const l of t.open??[])l.headersSent||(n+=1);return{unanswered:n+j(t),streams:Math.max(0,t.inflight-n),work:T(t.workGen).count}},"owedBy"),ne=i((t,n,l=`${n}s`)=>`${t} ${t===1?n:l}`,"plural");async function $e(){if(Z<=0)return;const t=Date.now();let n=!1;for(;!(G.size<ye);){const l=G.values().next().value;if(!l||l.closed)break;const p=oe(l);if(p.unanswered===0&&p.work===0)break;const m=(l.drainingSince??t)+Z-Date.now();if(m<=0)break;n||(n=!0,console.log(`[child] swap held \u2014 the app replaced ${Math.round((Date.now()-(l.drainingSince??t))/100)/10}s ago is still `+[p.unanswered?`answering ${ne(p.unanswered,"request")}`:"",p.work?`running ${ne(p.work,"background job")}`:""].filter(Boolean).join(" and ")+`, and this pod keeps at most ${ye} replaced app(s) alive (LENSMCP_SWAP_MAX_DRAINING). The new build is applied when it is done \u2014 in ${Math.ceil(m/1e3)}s at the latest (LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS).`)),await new Promise(E=>{setTimeout(E,Math.min(100,m)).unref?.()})}n&&console.log(`[child] swap resumed after ${Date.now()-t}ms`)}_($e,"xe"),i($e,"holdForOwedWork");const R=(0,swap_recycle_1.swapRecyclePolicy)();let pe,se=0;const We=(()=>{const t=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(t)&&t>=0?t:3500})();let f=!1,b=!1;const v=(0,pod_memory_1.podMemoryPolicy)();let q=0;const O=new Set;let ue=!1;const H=i(()=>{if(!ue)return;let t=0;for(const n of O)n.deref()===void 0?O.delete(n):t+=1;return t},"liveContainers"),k=(0,pod_memory_1.createPodMemoryMonitor)({policy:v,state:i(()=>({gen:U.gen??0,swaps:se,...pe===void 0?{}:{baseline:pe},busy:S||re||M.size>0,lastBusyAt:Y}),"state"),send:i(t=>{tell(process,t)},"send"),containers:H,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:i(()=>we(),"onRetained"),gc:i(()=>(0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc),"gc")}),L=i(()=>((0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc)&&k.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),W=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,n=`${path.sep}node_modules${path.sep}`,l=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:i(p=>l.has(p)||p.startsWith(t)&&!p.slice(t.length).includes(n)&&!p.slice(t.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let me=!1;function be(){if(!(!W||me))try{const t=W();t.errors>0&&console.log(`[child] read the stack of ${t.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(t){console.error("[child] could not release library error frames:",t),me=!0}}_(be,"Ne"),i(be,"releaseLibraryErrorFrames");const M=new Map;let Y=Date.now();const he=i(()=>R.settleMs<=0||!S&&!re&&M.size===0&&Date.now()-Y>=R.settleMs,"settled");let K;function ie(t){if(!(!R.enabled||f||b)){if(R.settleMs<=0){ee(t);return}K&&clearTimeout(K),K=setTimeout(()=>{K=void 0,he()?ee(t):ie(t)},R.settleMs),K.unref?.()}}_(ie,"de"),i(ie,"recycleCheckWhenSettled");const Me=i(()=>{if(R.stuckCloseMs<=0)return 0;const t=Date.now();let n=0;for(const l of M.values())t-l>=R.stuckCloseMs&&(n+=1);return n},"stuckCloses");function ee(t){if(!R.enabled||f||b||!process.send)return;const n=v8.getHeapStatistics().heap_size_limit,l=i(m=>{const E=k.oldestRetained();return{swaps:se,usedBytes:m,limitBytes:n,stuckCloses:Me(),settled:he(),...pe===void 0?{}:{baselineBytes:pe},...E?{retained:{generation:E.gen,ageMs:Date.now()-E.closedAt,gcs:E.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(R,l(process.memoryUsage().heapUsed)))return;const p=(0,swap_recycle_1.recycleReason)(R,l(L()));p&&(f=!0,console.log(`[child] asking for a fresh pod ${t} \u2014 ${p}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB / _RETAINED_MS, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),tell(process,{type:"recycle-request",reason:p}))}_(ee,"oe"),i(ee,"considerRecycle");let fe;function we(){if(!R.enabled||R.retainedMs<=0||f||b)return;const t=k.oldestRetained();if(!t)return;const n=t.closedAt+R.retainedMs-Date.now();if(n<=0){ee("with a closed generation still in memory");return}fe||(fe=setTimeout(()=>{fe=void 0,k.measure("retention-check").catch(()=>{})},n),fe.unref?.())}_(we,"Oe"),i(we,"retainedRecycleCheck");function _e(t,n){const l={};n&&(l.module=n),t&&(l.app=t);try{const p=t?.app;if(p&&typeof p=="object"&&(l.nestApp=p,p.container&&typeof p.container=="object")){l.container=p.container;const m=rootModuleOf(p.container);m&&(l.rootModule=m)}}catch{}return l}_(_e,"we"),i(_e,"generationTargets");async function V(t,n){if(t.closed||(t.closed=!0,G.delete(t),n&&console.error(`[child] ${n}`),!t.app))return;M.set(t,Date.now());const l=R.enabled&&R.stuckCloseMs>0?setTimeout(()=>{M.has(t)&&(console.error(`[child] a previous app has been closing for ${Math.round(R.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),ee("with a previous app stuck in close()"))},R.stuckCloseMs):void 0;l?.unref?.();const p=t.app;try{await t.app.close()}catch(m){console.error("[child] error closing a previous app:",m)}finally{M.delete(t),Y=Date.now(),l&&clearTimeout(l),t.app=null,t.abandoned=void 0,t.handler=(m,E)=>{E.statusCode=503,E.end("closed")},t.gen!==void 0&&k.closed(t.gen,se,_e(p,t.module)),t.module=void 0}if(t.superseded){try{ce((0,hot_swap_registries_1.pruneRegistries)(t.superseded,"serve"),"after closing the previous app")}catch(m){console.error("[child] could not release the previous app's class registrations:",m)}t.superseded=void 0}ie("after closing the previous app")}_(V,"ne"),i(V,"closeGeneration");async function ke(t){const n=Date.now();t.drainingSince=n;const l=i(()=>({inflight:t.inflight+j(t),work:T(t.workGen).count}),"busy"),p=l();if(p.inflight>0||p.work>0){for(;G.size>=ye;){const I=G.values().next().value;if(!I)break;const P=oe(I),te=Math.round((Date.now()-(I.drainingSince??n))/1e3);if(P.unanswered===0&&P.streams===0&&P.work===0){await V(I);continue}await V(I,`closing a still-draining generation early \u2014 ${G.size} were pending and the cap is ${ye} (each holds a whole app: DI container, DB pool, queue workers). `+(P.unanswered>0||P.work>0?`It was replaced ${te}s ago and had still not answered ${ne(P.unanswered,"request")}`+(P.work?` or finished ${ne(P.work,"background job")}`:"")+` \u2014 those fail now (a queue job keeps its lock until the TTL lapses). Raise LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS (currently ${Z}ms) to wait longer for them. `:`It held only ${ne(P.streams,"open stream")}; their clients reconnect to the new app. `)+"Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.")}G.add(t)}for(;le>0&&Date.now()-n<le;){if(t.closed)return;const I=l();if(I.inflight===0&&I.work===0)break;await new Promise(P=>{setTimeout(P,250).unref?.()})}if(t.closed)return;const m=l(),E=de(t.workGen);m.inflight>0||m.work>0?console.error(`[child] swap drain expired after ${Date.now()-n}ms \u2014 closing the previous app with ${m.inflight} in-flight request(s) and ${m.work} background job(s) still running. `+(E?`Previous app still processing ${E.running} queue job(s); ${E.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${le}ms) if this is legitimate work.`):(Date.now()-n>250||E?.runningAtStop||E?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-n}ms \u2014 closing it`+(E?` (it finished ${E.finishedAfterStop} queue job(s) after the swap; ${E.takenAfterStop} were taken after the swap)`:"")),await V(t)}_(ke,"Le"),i(ke,"drainThenClose");async function J(){if(S)return re=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),g;S=!0,Y=Date.now(),await $e(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(N,z)=>{console.error(`[child] uncaughtException (${z}):`,N)}),process.on("unhandledRejection",N=>{const z=N instanceof Error?N.stack||N.message:String(N);z.includes("FronteggContextScope")||z.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",N)});let t;if(Le)try{t=(0,hot_swap_registries_1.captureRegistries)()}catch{t=void 0}const n=global.__lensmcpBeginWorkGeneration,l=typeof n=="function"?n():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(N){if(!(N instanceof SyntaxError))throw N;console.warn(`[child] bundle was unreadable mid-write (${N.message}) \u2014 retrying the swap once`),await new Promise(z=>setTimeout(z,400)),await importFresh(BUNDLE_PATH)}}catch(N){throw S=!1,Y=Date.now(),N}if(typeof global.createChildApp!="function")throw S=!1,Y=Date.now(),new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);let p;if(t)try{p=(0,hot_swap_registries_1.supersededSince)(t),ce((0,hot_swap_registries_1.pruneRegistries)(p,"build"),"at the swap")}catch(N){console.error("[child] could not release superseded class registrations:",N)}let m;try{m=await global.createChildApp()}catch(N){throw S=!1,Y=Date.now(),takeImportedModule(),N}const E=U;p?.size&&(E.app?E.superseded=p:ce((0,hot_swap_registries_1.pruneRegistries)(p,"serve"),"at the swap")),g=m;const I=takeImportedModule();U={app:m,handler:m.handler,inflight:0,gen:++q,...I?{module:I}:{},...l===void 0?{}:{workGen:l}};const P=_e(m,void 0).container;P&&(O.add(new WeakRef(P)),ue=!0);const te=E.app&&l!==void 0?ve(l):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(te?.workers?`; its ${te.workers} queue worker(s) stopped taking new jobs, previous app still processing ${te.runningAtStop} job(s)`:"")),be(),E.app&&(se+=1,ke(E).catch(N=>console.error("[child] background close error:",N)),setTimeout(()=>ee("30s after a hot swap"),3e4).unref?.(),k.swapped(E.gen,()=>E.closed===!0&&!M.has(E))),S=!1,Y=Date.now(),re?(re=!1,J()):m}_(J,"q"),i(J,"swapNow");const Fe=debounce(async()=>{try{console.log("[child] /webpack/reload"),await J()}catch(t){console.error("[child] reload failed:",t)}},250),B=http.createServer((t,n)=>{if(t.method==="POST"&&t.url==="/webpack/reload"){n.writeHead(200,{"content-type":"application/json"}),n.end(JSON.stringify({ok:!0})),Fe();return}if(t.method==="GET"&&t.url==="/__lensmcp/alive"){const p=t.headers["x-internal-token"],m=process.env.LENSMCP_SERVICE_KEY;if(m&&p!==m){n.writeHead(404,{"content-type":"text/plain"}),n.end("not found");return}const E=T();n.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),n.end(JSON.stringify({ok:!0,pid:process.pid,swapping:S,inflight:U.inflight,oldestInflightAt:U.oldestAt,activeWork:E.count,oldestWorkAt:E.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...F?{watcher:F}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const l=U;l.inflight+=1,(l.open??=new Set).add(n),l.oldestAt===void 0&&(l.oldestAt=Date.now()),n.on("close",()=>{l.inflight-=1,l.open?.delete(n),Z>0&&!l.closed&&!n.headersSent&&!n.writableEnded&&((l.abandoned??=new Map).set(n,Date.now()),l.abandoned.size>256&&j(l)),l.inflight===0&&(l.oldestAt=void 0),k.activity()}),l.handler(t,n)});B.keepAliveTimeout=0;const $=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock($),process.exit(0)});try{const t=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const n=(0,schema_first_instance_1.prewarmZodFor)(t);n?.created&&console.log(`[child] zod${n.version?` ${n.version}`:""}: built ${n.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const n=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(t);n?.patched&&console.log(`[child] ioredis${n.versions.length?` ${n.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await J(),pe=(await k.booted()).live,console.log(`[child] Using bundle: ${t}`),claimSock($),B.listen($,()=>{console.log(`[child] Listening on ${$}`),tell(process,{type:"ready",socketPath:$})})}catch(t){console.error("[child] Startup error:",t),tell(process,{type:"boot-error",error:String(t?.stack||t)}),process.exitCode=1;return}process.on("message",async t=>{if(!(!t||typeof t!="object")){if(t.type==="debug-open"){const n=Number(t.port);a(Number.isInteger(n)&&n>1024?n:0);return}if(t.type==="watcher"){const n=t.testimony;n&&typeof n=="object"&&(F=n);return}if(t.type==="heap-snapshot"){X(t);return}if(t.type==="recycle-declined"){f=!1;return}if(t.type==="retire"){ge();return}if(t.type==="reload"){if(b)return;try{const n=Date.now();await J(),tell(process,{type:"reloaded",socketPath:$,swap:{generation:q,swaps:se,swapMs:Date.now()-n,draining:G.size}})}catch(n){console.error("[child] reload error:",n),tell(process,{type:"reload-error",error:String(n?.stack||n)})}}}});let Ee=!1;async function X(t){const n=i(l=>{tell(process,{type:"heap-snapshot-result",id:t.id,...l})},"reply");if(Ee)return n({ok:!1,refused:"a snapshot of this pod is already being written"});Ee=!0;try{const l=L(),p=k.tracker.views().map(P=>P.gen),m=(0,heap_snapshot_1.heapSnapshotPolicy)();t.maxHeapBytes&&(m.maxHeapBytes=t.maxHeapBytes);const E=(0,heap_snapshot_1.heapSnapshotRefusal)(m,{live:l,...t.available?{available:t.available}:{}});if(E)return n({ok:!1,refused:E,live:l});console.log(`[child] writing a heap snapshot (${Math.round(l/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const I=await(0,heap_snapshot_1.writeHeapSnapshot)(t.file);n({ok:!0,file:t.file,bytes:I.bytes,ms:I.ms,live:l,gen:U.gen??0,closedAlive:p})}catch(l){n({ok:!1,error:l?.message??String(l)})}finally{Ee=!1}}_(X,"z"),i(X,"takeHeapSnapshot");async function ge(t="a fresh pod has taken over"){if(b)return;b=!0,k.stop(),console.log(`[child] retiring \u2014 ${t}; ${path.basename($,".sock")} (pid ${process.pid}) is finishing in-flight work, then exiting`);const n=ve(Number.POSITIVE_INFINITY);n?.workers&&console.log(`[child] its ${n.workers} queue worker(s) stopped taking new jobs, still processing ${n.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath($),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(p=>{setTimeout(p,We).unref?.()}),cleanupSock($),B.close();const l=Date.now();for(;Date.now()-l<le;){B.closeIdleConnections?.();let p=0;for(const m of G)p+=oe(m).unanswered;if(U.inflight+j(U)===0&&p===0&&T().count===0)break;await new Promise(m=>{setTimeout(m,250).unref?.()})}setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(p){console.error("[child] close error while retiring:",p)}process.exit(0)}_(ge,"_e"),i(ge,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const Ce=i(async t=>{console.log(`[child ${t}] shutting down\u2026`);const n=g?.close().catch(p=>console.error("[child] close error:",p)),l=new Promise(p=>B.close(()=>p()));cleanupSock($),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([n,l]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(t=>{process.on(t,()=>{if(t==="SIGTERM"&&!b&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){ge("this generation was handed over to a newer one");return}inspector?.close?.(),Ce(t)})})})()}else{let a=_(function(f){j.push(f),j.length>Z&&j.splice(0,j.length-Z),ye?.write((0,inspector_secret_1.redactInspectorUrls)(f.replace(G,"")));for(const b of oe)try{const v=b.plain?f.replace(G,""):f;b.res.write(b.redact?(0,inspector_secret_1.redactInspectorUrls)(v):v)}catch{oe.delete(b)}},"pe");i(a,"emitLogLine");const h=12,g=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",S=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),F=i(f=>({project:g,service:SERVICE_NAME||g,pod:f.id,pid:f.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),re=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],Le="\x1B[0m",ce=Math.max(1,Number(process.env.CHILD_COUNT||1)),U=LENSMCP_WS_KEY||path.basename(process.cwd()),le=i(f=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(U,SERVICE_NAME||"service",f)),"execPathFor");let T=(()=>{const f=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(f)&&f>1024?f:0})();const ve=20;T&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/closed/codex-security-06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${T}+slot, workers ${T}+${ve}+i). Any account on this host can connect to 127.0.0.1:${T} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const de=process.env.LENSMCP_LOG_FILE||"",ye=de?(()=>{try{fs.mkdirSync(path.dirname(de),{recursive:!0});const f=fs.createWriteStream(de,{flags:"a",mode:384});try{fs.chmodSync(de,384)}catch{}return f}catch(f){return console.warn(`[parent] cannot open log file ${de}:`,f.message),null}})():null,G=/\x1b\[[0-9;]*m/g,Z=2e3,j=[],oe=new Set,ne=(0,devserver_transport_1.gatewayManaged)();for(const f of["log","info","warn","error"]){const b=console[f].bind(console),v=f==="log"||f==="info"?process.stdout:process.stderr;console[f]=(...q)=>{b(...q.map(O=>typeof O=="string"?(0,inspector_secret_1.forStream)(v,ne,O):O)),a(q.map(O=>typeof O=="string"?O:util.inspect(O)).join(" ")+`
|
|
13
|
+
`)}}const $e=i(f=>re[(f-1)%re.length],"colorFor"),R=i(f=>{if(ce===1)return"";const b=`[child#${f}] `;return`${$e(f)}${b}${Le}`},"tagFor"),pe=i(f=>f.startsWith("{")&&f.endsWith("}"),"isJsonLog"),se=i((f,b,v)=>{const q=prettyFactory({sync:!0,colorize:!0,crlf:!0,messageKey:"message",errorLikeObjectKeys:["err","error"],errorProps:"type,message,stack",ignore:["logContext","context","hostname","req","res","err.driverError","module","cloudEnvironment","frontegg-application-id","frontegg-tenant-id","frontegg-trace-id","frontegg-vendor-id","host","service","version","err","error"].join(","),messageFormat:"{if module}[{module}] {end}{if context}[{context}] {end}{if logContext}[{logContext}] {end}{message}",customPrettifiers:{stack:i(H=>`
|
|
14
|
+
`+String(H),"stack")}}),O=i((H,k)=>{const L=H==="stdout"?process.stdout:process.stderr;try{v?.(k)}catch{}const W=pe(k)?q(k):`${k}
|
|
15
|
+
`;L.write((0,inspector_secret_1.forStream)(L,ne,`${b}${W}`)),a(`${b}${W}`)},"write"),ue=i((H,k)=>{if(!H)return;const L=readline.createInterface({input:H});L.on("line",W=>O(k,W)),L.on("close",()=>{})},"attach");ue(f.stdout,"stdout"),ue(f.stderr,"stderr")},"wireProcLogging"),We=i(f=>se(f.proc,R(f.id),b=>f.tail.push(b)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const f=httpProxy.createProxyServer({});let b=Number(process.env.PORT)||0;b||(b=9090);const v=[],q=new Set;function O(){let e=0;for(;q.has(e);)e++;return q.add(e),e}_(O,"I"),i(O,"allocDebugSlot");let ue=1,H=0,k=!1,L=ce,W=[],me=0;const be=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),M=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),Y=i(e=>e.booting&&!M(e)&&Date.now()-e.spawnedAt<be,"stillBooting");function he(){return v.filter(e=>e.healthy&&typeof e.socketPath=="string")}_(he,"he"),i(he,"healthyChildren");let K,ie=0,Me;const ee={project:g,service:SERVICE_NAME||g,podGeneration:POD_GENERATION};function fe(e){const r=(0,service_build_events_1.buildAccountOf)(e.building);r&&r.startedAt!==ie&&(ie=r.startedAt,S.publish((0,service_build_events_1.buildServiceBuildEvent)(ee,"started",r,{root:process.cwd()})));const c=(0,service_build_events_1.buildAccountOf)(e.failed);c&&(ie=0,S.publish((0,service_build_events_1.buildServiceBuildEvent)(ee,"failed",c,{root:process.cwd()})))}_(fe,"fe"),i(fe,"recordBuildProgress");function we(e){if(!e||typeof e!="object")return;const r=e;if(r.schemaVersion===1){fe(r),"failed"in r&&delete r.failed,K=r;for(const c of v)tell(c.proc,{type:"watcher",testimony:r})}}_(we,"Oe"),i(we,"recordWatcherTestimony");function _e(e,r=256*1024){return new Promise(c=>{let u="",s=!1;e.on("data",o=>{s||(u+=o,u.length>r&&(s=!0))}),e.on("end",()=>{if(s||!u)return c(void 0);try{c(JSON.parse(u))}catch{c(void 0)}}),e.on("error",()=>c(void 0))})}_(_e,"we"),i(_e,"readJsonBody");function V(){const e=he();if(e.length===0)return null;const r=H%e.length;return H=(H+1)%e.length,e[r]}_(V,"ne"),i(V,"pickChild");function ke(e,r){const c=`Child#${e.id}`,u=SERVICE_NAME||g,s=Date.now();if(r.type==="pod-memory"){const o=r.report;if(!o||typeof o.live!="number")return;e.memory.latest=o,e.memory.history.push(o),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(c,o)}`),S.publish((0,pod_memory_events_1.buildPodMemoryEvent)(F(e),o))}else if(r.type==="pod-memory-retained"){const o=r.retained;if(!o||typeof o.gen!="number")return;e.memory.retained.set(o.gen,{...o,at:s}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(c,u,o)}`),S.publish((0,pod_memory_events_1.buildPodRetainedEvent)(F(e),o,s))}else{const o=r.released;if(!o||typeof o.gen!="number")return;e.memory.retained.delete(o.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(c,u,o)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(F(e),o,s))}}_(ke,"Le"),i(ke,"recordPodMemory");const J=new Map;let Fe=0;async function B(){const e=ue++,r=childSockPath(e);cleanupSock(r);const c=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&c.unshift("--require","@swc-node/register"),c.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const u=T?O():-1,s=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:r,...u>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(T,u))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:c,execPath:le("pod")}),o={id:e,proc:s,healthy:!1,lastError:null,debugSlot:u,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:me,memory:{history:[],retained:new Map}};return s.on("error",w=>console.error(`[parent] Child#${e}: ${w.message}`)),s.on("message",w=>{if(!w||typeof w!="object")return;const d=w;if(d.type==="ready")o.socketPath=String(d.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),K&&tell(s,{type:"watcher",testimony:K}),me!==o.bootReloadSeq&&tell(s,{type:"reload"});else if(d.type==="recycle-request")l(o,String(d.reason||"it has outgrown its bound"));else if(d.type==="inspector-url")o.inspectorUrl=String(d.url),console.log(`[parent] Child#${o.id} Debugger listening on ${d.url}`);else if(d.type==="reloaded"){if(X.has(o)){console.log(`[parent] Child#${o.id} finished a swap it began before it was retired \u2014 not serving it: a fresh pod has taken over`);return}o.socketPath=String(d.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`);const y=d.swap;y&&typeof y.generation=="number"&&typeof y.swaps=="number"&&S.publish((0,service_build_events_1.buildServiceSwapEvent)(F(o),{generation:y.generation,swaps:y.swaps,swapMs:typeof y.swapMs=="number"?y.swapMs:0,draining:typeof y.draining=="number"?y.draining:0},Me,{root:process.cwd()}))}else d.type==="reload-error"?(o.lastError=String(d.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):d.type==="boot-error"?(o.lastError=String(d.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1):d.type==="pod-memory"||d.type==="pod-memory-retained"||d.type==="pod-memory-released"?ke(o,d):d.type==="heap-snapshot-result"&&J.get(Number(d.id))?.(d)}),s.on("exit",(w,d)=>{o.booting=!1;for(const C of o.memory.retained.values()){const D={gen:C.gen,closedAt:C.closedAt,ageMs:Date.now()-C.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${o.id}`,SERVICE_NAME||g,D)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(F(o),D,Date.now()))}o.memory.retained.clear(),o.memory.latest&&S.publish((0,pod_memory_events_1.buildPodExitedEvent)(F(o),{code:w,signal:d},Date.now())),o.debugSlot>=0&&(q.delete(o.debugSlot),o.debugSlot=-1);const y=d==="SIGINT"||d==="SIGTERM"||w===0;if(y?console.log(`[parent] Child#${o.id} exited cleanly (code=${w}, signal=${d??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${w}, signal=${d??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,w,d,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${w}, signal=${d??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!y&&!k){const C=v.indexOf(o);C>=0&&v.splice(C,1);const D=Date.now();if(W=W.filter(Q=>D-Q<3e4),W.length>=5){console.error(`[parent] crash-loop detected (${W.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}W.push(D),setTimeout(()=>{const Q=v.filter(Te=>Te.proc.killed===!1).length;!k&&Q<L&&(console.log(`[parent] respawning a crashed pod (${Q}/${L} live)`),B().catch(()=>{}))},1500).unref?.()}}),v.push(o),We(o),o}_(B,"K"),i(B,"spawnChild");async function $(e){L=Math.max(L,e);const r=v.filter(u=>u.proc.killed===!1),c=e-r.length;for(let u=0;u<c;u++)await B()}_($,"M"),i($,"ensurePoolSize");async function Ee(){const e=v.filter(r=>(!r.healthy||typeof r.socketPath!="string")&&!Y(r));await Promise.all(e.map(async r=>{try{r.proc.kill("SIGTERM")}catch{}cleanupSock(r.socketPath,r.proc.pid),r.socketPath=void 0;const c=v.indexOf(r);c>=0&&v.splice(c,1),await B()}))}_(Ee,"Ee"),i(Ee,"respawnUnhealthy");const X=new Set;let ge=!1,Ce=0;const t=3e4,n=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function l(e,r){const c=i(s=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${r}) \u2014 not now: ${s}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(k||!v.includes(e))return;if(ge)return c("another pod is already being replaced");const u=Date.now()-Ce;if(u<t)return c(`a pod was replaced ${Math.round(u/1e3)}s ago`);ge=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${r}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const s=await B(),o=Date.now()+be;for(;!s.healthy&&!s.lastError&&!M(s)&&Date.now()<o;)await new Promise(y=>{setTimeout(y,100).unref?.()});if(k)return;if(!s.healthy){const y=v.indexOf(s);y>=0&&v.splice(y,1);try{s.proc.kill("SIGTERM")}catch{}return c(`its successor Child#${s.id} did not come up (${s.lastError??"no answer in time"})`)}const w=v.indexOf(e);w>=0&&v.splice(w,1),e.healthy=!1,X.add(e),postRetiringNotice(e.socketPath,e.proc.pid);const d=setTimeout(()=>{if(!M(e)){console.error(`[parent] Child#${e.id} was still running ${n}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},n);if(d.unref?.(),e.proc.once("exit",()=>{clearTimeout(d),X.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}Ce=Date.now(),console.log(`[parent] Child#${s.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${s.proc.pid})`)}catch(s){c(`could not start a successor: ${s?.message??String(s)}`)}finally{ge=!1}}_(l,"l"),i(l,"recyclePod");const p=[];function m(e){const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),c=path.join(r,`${e}.js`);if(!fs.existsSync(c))return console.error(`[parent] Worker "${e}" bundle not found: ${c}`),null;const u=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&u.unshift("--require","@swc-node/register");try{u.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const s=inspectorPortFor(T||9229,ve+Math.max(0,WORKER_NAMES.indexOf(e)));u.push(T?`--inspect=127.0.0.1:${s}`:`--inspect-port=127.0.0.1:${s}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const o=(0,node_child_process_1.fork)(c,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:u,execPath:le(`w-${e}`)}),w={name:e,proc:o,debugPort:s};return p.push(w),o.on("error",d=>console.error(`[parent] Worker "${e}": ${d.message}`)),se(o,`\x1B[35m[worker:${e}] \x1B[0m`,d=>{const y=/Debugger listening on (wss?:\/\/\S+)/.exec(d);y&&(w.inspectorUrl=y[1])}),o.on("exit",(d,y)=>{y==="SIGINT"||y==="SIGTERM"||d===0||console.error(`[parent] Worker "${e}" crashed (code=${d}, signal=${y??"none"})`);const C=p.indexOf(w);C>=0&&p.splice(C,1)}),console.log(`[parent] Worker "${e}" started (pid=${o.pid})`),w}_(m,"g"),i(m,"spawnWorkerProcess");const E=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),I=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),P=new Set,te=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function N(e,r){if(P.delete(e),!te(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${r} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}_(N,"R"),i(N,"killDrainingWorker");function z(e){try{e.proc.kill("SIGTERM")}catch{}if(te(e))return;const r=[...P].filter(u=>u.name===e.name);for(;r.length>=I;)N(r.shift(),`was still running when a later reload replaced its successor (cap ${I})`);P.add(e);const c=setTimeout(()=>N(e,`was still running ${E}ms after SIGTERM`),E);c.unref?.(),e.proc.once("exit",()=>{clearTimeout(c),P.delete(e)})}_(z,"B"),i(z,"retireWorker");function qe(){for(const e of[...p])z(e);p.length=0;for(const e of WORKER_NAMES)m(e)}_(qe,"Ve"),i(qe,"restartWorkers");const Xe=debounce(async()=>{me+=1;const e=he();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const r of e)tell(r.proc,{type:"reload"})}await Ee(),WORKER_NAMES.length>0&&qe()},200),Be=i((e,r)=>{const c=(0,notice_generation_1.noticeGeneration)(e.headers);return c===void 0||c===POD_GENERATION?!1:(e.resume(),r.writeHead(409,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!1,generation:POD_GENERATION,sentFor:c})),e.url==="/webpack/reload"&&console.log(`[parent] a build of generation g${c} reached generation g${POD_GENERATION} \u2014 not reloaded: it is not this generation's build`),!0)},"refusedForAnotherGeneration"),Pe=i(async(e,r)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);if(Be(e,r))return;r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0})),_e(e).then(s=>{const o=s;Me=(0,service_build_events_1.buildAccountOf)(o?.build),ie=0,we(o?.watcher)}),Xe();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);if(Be(e,r))return;const s=await _e(e);we(s),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,relayed:K!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);let s="";e.on("data",o=>{s+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(s||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await $(o),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const s=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(s.get("tail")??"100")||0,0),Z),w=s.get("follow")!=="0",d=s.get("plain")==="1",y=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);r.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const C=o===0?"":j.slice(-o).join(""),D=d?C.replace(G,""):C;if(r.write(y?(0,inspector_secret_1.redactInspectorUrls)(D):D),!w){r.end();return}const Q={res:r,plain:d,redact:y};oe.add(Q),e.on("close",()=>oe.delete(Q));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const s=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(s)&&s>1024&&(T=s),T||(T=9229);const o=v.filter(d=>d.proc.exitCode===null&&d.proc.signalCode===null);for(const d of o)d.debugSlot<0&&(d.debugSlot=O()),tell(d.proc,{type:"debug-open",port:inspectorPortFor(T,d.debugSlot)});for(const d of p)try{d.proc.kill("SIGUSR1")}catch{}const w=Date.now()+2e3;for(;Date.now()<w&&o.some(d=>!d.inspectorUrl);)await new Promise(d=>setTimeout(d,50));r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,basePort:T,fixedPorts:usesFixedInspectorPorts(),pods:o.map(d=>({id:d.id,inspectorUrl:d.inspectorUrl??null})),workers:p.map(d=>({name:d.name,port:d.debugPort||(0,inspector_secret_1.inspectorPortOf)(d.inspectorUrl),inspectorUrl:d.inspectorUrl??null}))},null,2)+`
|
|
16
|
+
`);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const s=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),o=[...v,...X].map(w=>({id:w.id,pid:w.proc.pid??0,podGeneration:POD_GENERATION,healthy:w.healthy,retiring:X.has(w),latest:w.memory.latest??null,history:w.memory.history,retained:[...w.memory.retained.values()]}));r.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),r.end(JSON.stringify({ok:!0,service:SERVICE_NAME,project:g,bundle:{path:s},pods:o},null,2)+`
|
|
17
|
+
`);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const s=i((x,Ie)=>{r.writeHead(x,{"content-type":"application/json"}),r.end(JSON.stringify(Ie,null,2)+`
|
|
18
|
+
`)},"reply"),o=new URL(e.url,"http://localhost").searchParams,w=Number(o.get("pod")||""),d=o.has("maxMb")?Number(o.get("maxMb")):void 0;if(d!==void 0&&!(Number.isFinite(d)&&d>0)){s(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const y=Number.isInteger(w)&&w>0?v.find(x=>x.id===w):he()[0];if(!y||M(y)){s(404,{ok:!1,error:w?`no pod #${w} \u2014 running: ${v.map(x=>`#${x.id}`).join(", ")||"none"}`:"no pod is serving"});return}if(J.size>0){s(409,{ok:!1,refused:"a heap snapshot is already being written for this service"});return}const C=(0,heap_snapshot_1.heapSnapshotPolicy)(),D=(0,heap_snapshot_1.heapSnapshotDir)();let Q;try{(0,heap_snapshot_1.ensureHeapDir)(D);const x=(0,heap_snapshot_1.pruneHeapSnapshots)(D,C);x.length&&console.log(`[parent] removed ${x.length} older heap snapshot(s) from ${D}`),Q=path.join(D,(0,heap_snapshot_1.heapSnapshotFileName)(SERVICE_NAME||g,y.id,y.proc.pid??0,y.memory.latest?.gen??0,Date.now()))}catch(x){s(500,{ok:!1,error:`cannot prepare ${D}: ${x.message}`});return}const Te=++Fe,Ge=(0,heap_snapshot_1.availableSystemMemory)();let Oe;const A=await new Promise(x=>{const Ie=setTimeout(()=>x(void 0),6e5);Ie.unref?.(),J.set(Te,tt=>{clearTimeout(Ie),x(tt)}),Oe=i(()=>{clearTimeout(Ie),x(void 0)},"onExit"),y.proc.once("exit",Oe);const Ze={type:"heap-snapshot",id:Te,file:Q,...Ge?{available:Ge}:{},...d!==void 0?{maxHeapBytes:Math.floor(d*1048576)}:{}};tell(y.proc,Ze)||Oe()}).finally(()=>{J.delete(Te),Oe&&y.proc.off("exit",Oe)});if(!A){s(504,{ok:!1,pod:y.id,error:"the pod did not finish the snapshot (it exited, or took longer than 10 minutes)"});return}A.ok?console.log(`[parent] Child#${y.id} wrote a ${Math.round((A.bytes??0)/1048576)} MB heap snapshot in ${A.ms} ms \u2192 ${A.file}`):console.warn(`[parent] Child#${y.id} heap snapshot not taken: ${A.refused??A.error}`),s(A.ok?200:A.refused?409:500,{ok:A.ok,pod:y.id,pid:y.proc.pid??0,service:SERVICE_NAME,bundle:path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),...Ge?{available:Ge}:{},...A.file?{file:A.file,bytes:A.bytes,ms:A.ms}:{},...A.live!==void 0?{live:A.live}:{},...A.gen!==void 0?{gen:A.gen,closedAlive:A.closedAlive??[]}:{},...A.refused?{refused:A.refused}:{},...A.error?{error:A.error}:{}});return}const c=matchGatewayRoute(e);if(c?.prependPrefix&&!e.url?.startsWith(c.prependPrefix)&&(e.url=c.prependPrefix+(e.url??"/")),c?.target){applyGatewayMiddleware(e),f.web(e,r,{target:c.target,autoRewrite:typeof c.target=="string",changeOrigin:!0},s=>{console.error(`[parent] proxy error to ${c.target}:`,s),r.statusCode=502,r.setHeader("content-type","text/plain; charset=utf-8"),r.end(`Upstream error from ${c.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){r.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),r.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const u=V();if(!u){r.statusCode=503,r.setHeader("content-type","text/plain; charset=utf-8");const s=v.map(o=>`#${o.id}: ${o.lastError??"no error recorded"}`).join(`
|
|
19
19
|
`);r.end(`No healthy children available. POST /webpack/reload to recover.
|
|
20
20
|
|
|
21
21
|
Last errors:
|
|
22
|
-
${s}`);return}
|
|
23
|
-
${u.lastError}`)})},"requestHandler");let ae,
|
|
22
|
+
${s}`);return}f.web(e,r,{target:{socketPath:u.socketPath},headers:{"x-proxy-child-id":String(u.id)}},s=>{console.error(`[parent] proxy error to Child#${u.id}:`,s),u.healthy=!1,u.lastError=String(s?.stack||s),r.statusCode=502,r.setHeader("content-type","text/plain; charset=utf-8"),r.end(`Upstream error from Child#${u.id}. Try /webpack/reload.
|
|
23
|
+
${u.lastError}`)})},"requestHandler");let ae,Ne,He;const Ae=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),r=GATEWAY_ROUTES.map(u=>u.host).filter(u=>!!u),c=(0,checkout_cache_1.checkoutCacheDir)(process.cwd(),"davnx-webpack");Ne=e.getCertificateSync(c,SERVICE_NAME||"lensmcp.dev",r),He=e.caCertPath(),ae=require("node:https").createServer({key:Ne,cert:Ne},Pe)}else ae=http.createServer(Pe);const je=i((e,r,c)=>{const u=matchGatewayRoute(e);if(u?.prependPrefix&&!e.url?.startsWith(u.prependPrefix)&&(e.url=u.prependPrefix+(e.url??"/")),u?.target){f.ws(e,r,c,{target:u.target,changeOrigin:!0},()=>r.destroy());return}const s=V();if(!s){r.destroy();return}f.ws(e,r,c,{target:{socketPath:s.socketPath}},()=>r.destroy())},"upgradeHandler");ae.on("upgrade",je);const Ye=2e3;let Ke=!1;ae.on("error",e=>{if(e.code==="EADDRINUSE"){Ke||(Ke=!0,console.warn(`[parent] public port ${b} already in use \u2014 continuing on the unix-socket pool only (gateway routing is unaffected) and retrying the bind every ${Ye/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!k&&!ae.listening&&ae.listen(b)},Ye).unref?.();return}console.error("[parent] public server error:",e)}),await $(ce);for(const e of WORKER_NAMES)m(e);SOCKET_ONLY?console.log(`[parent] socket-only: no TCP listener (port ${b} is not bound \u2014 the gateway reaches this service through its socket pool)`):ae.listen(b,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${b}`),SERVICE_PREFIX&&console.log(`[parent] Service prefix: "${SERVICE_PREFIX}" (enforced \u2014 requests without it will get 404)`),gatewayMiddleware&&console.log(`[parent] Gateway middleware: ACTIVE (${GATEWAY_MIDDLEWARE_PATH})`),GATEWAY_ROUTES.length>0&&console.log(`[parent] Gateway routes: ${GATEWAY_ROUTES.map(r=>`${r.host??"*"}${r.prefix??"/"}\u2192${r.target??"children"}`).join(" ")}`),He&&(console.log("[parent] HTTPS dev CA (trust ONCE, then rotations/new hostnames stay green):"),console.log(`[parent] sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${He}`)),console.log(`POST ${e}://localhost:${b}/webpack/reload to trigger rolling swap/respawn`)});const De=Number(process.env.INTERNAL_PORT||0);if(De>0&&!SOCKET_ONLY){const e=i((c,u)=>{const s=V();if(!s){u.statusCode=503,u.setHeader("content-type","text/plain; charset=utf-8"),u.end("No healthy children available.");return}f.web(c,u,{target:{socketPath:s.socketPath}},o=>{s.healthy=!1,s.lastError=String(o?.stack||o),u.statusCode=502,u.end(`Upstream error from Child#${s.id}.`)})},"internalHandler"),r=http.createServer(e);r.on("upgrade",(c,u,s)=>{const o=V();if(!o){u.destroy();return}f.ws(c,u,s,{target:{socketPath:o.socketPath}},()=>u.destroy())}),r.on("error",c=>{console.warn(`[parent] internal port ${De} unavailable (${c.code}).`)}),r.listen(De,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${De}`)}),Ae.push(r)}const ze=SOCKET_ONLY?[]:(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of ze){if(!Number.isInteger(e)||e===Number(b))continue;const r=GATEWAY_HTTPS?require("node:https").createServer({key:Ne,cert:Ne},Pe):http.createServer(Pe);r.on("upgrade",je),r.on("error",c=>{console.warn(`[parent] extra port ${e} unavailable (${c.code}) \u2014 main port still up.`)}),r.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),Ae.push(r)}const Se=path.join(SOCK_DIR,"parent.sock");claimSock(Se),(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);const Re=http.createServer(Pe);Re.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),Re.listen(Se),Ae.push(Re);function Ve(){k=!0;const e=[...v,...X];console.log(`[SIGTERM] [parent] generation g${POD_GENERATION} was handed over to a newer one \u2014 ${e.length} pod(s) finish their in-flight work, then it exits`);const r=sockOwnedByAnother(Se);cleanupSock(Se);for(const s of Ae)if(!(s===Re&&r))try{s.close()}catch{}try{ae.close()}catch{}for(const s of[...p])z(s);p.length=0;for(const s of v)if(!tell(s.proc,{type:"retire"}))try{s.proc.kill("SIGTERM")}catch{}const c=setTimeout(()=>{for(const s of e)if(!M(s)){console.error(`[parent] Child#${s.id} was still running ${n}ms after its generation was retired \u2014 killing it`);try{s.proc.kill("SIGKILL")}catch{}}},n);c.unref?.();const u=i(()=>{if(!(e.some(s=>!M(s))||[...P].some(s=>!te(s)))){clearTimeout(c);for(const s of e)s.socketPath&&cleanupSock(s.socketPath,s.proc.pid);(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);try{fs.rmdirSync(SOCK_DIR)}catch{}console.log(`[parent] generation g${POD_GENERATION} retired`),process.exit(0)}},"finish");for(const s of e)s.proc.once("exit",u);for(const s of P)s.proc.once("exit",u);u()}_(Ve,"qe"),i(Ve,"retireGeneration");const Je=i(async e=>{k=!0,console.log(`[${e}] [parent] shutting down\u2026`);const r=sockOwnedByAnother(Se);cleanupSock(Se);for(const c of Ae)if(!(c===Re&&r))try{c.close()}catch{}for(const c of p)try{c.proc.kill("SIGTERM")}catch{}for(const c of[...P])N(c,"was still draining when its devserver shut down");for(const c of X)try{c.proc.kill("SIGTERM")}catch{}for(const c of v){try{c.proc.kill("SIGTERM")}catch{}c.socketPath&&cleanupSock(c.socketPath,c.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}setTimeout(()=>process.exit(0),700).unref()},"shutdown");let Ue=0;const Qe=2e3;["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{if(e==="SIGTERM"){if(!k&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){Ue=Date.now(),Ve();return}if(Ue>0&&Date.now()-Ue<Qe)return}Je(e)}));const xe=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(xe)&&xe>0){const e=setInterval(()=>{try{process.kill(xe,0)}catch(r){if(r.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${xe}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),Je("gateway-gone")}},5e3);e.unref()}})()}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/cluster",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@lensmcp/core": "1.
|
|
61
|
-
"@lensmcp/node-instrumentation": "1.
|
|
62
|
-
"@lensmcp/nx-plugin": "1.
|
|
60
|
+
"@lensmcp/core": "1.25.0",
|
|
61
|
+
"@lensmcp/node-instrumentation": "1.25.0",
|
|
62
|
+
"@lensmcp/nx-plugin": "1.25.0",
|
|
63
63
|
"fastify": "^5.8.5",
|
|
64
64
|
"@fastify/reply-from": "^12.6.2",
|
|
65
65
|
"@typescript/native-preview": "^7.0.0-dev.20260707.2",
|
package/pod-memory.d.ts
CHANGED
|
@@ -231,6 +231,8 @@ export interface PodMemoryHost {
|
|
|
231
231
|
counter?: LiveObjectCounter;
|
|
232
232
|
/** A generation was just reported retained — the recycle rule's cue. */
|
|
233
233
|
onRetained?(oldest: ClosedGenerationView): void;
|
|
234
|
+
/** How this pod runs a full GC — the devserver's announces it beside its socket (gc-notice.ts). Default `fullGc`. */
|
|
235
|
+
gc?(): boolean;
|
|
234
236
|
now?: () => number;
|
|
235
237
|
}
|
|
236
238
|
/** Drives the collections and the reports. One per pod. */
|
package/pod-memory.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var O=Object.defineProperty;var d=(s,e)=>O(s,"name",{value:e,configurable:!0});var I=Object.defineProperty,i=d((s,e)=>I(s,"name",{value:e,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GenerationTracker=exports.LensmcpClosedGeneration=exports.RETAINED_MIN_GCS=void 0,exports.podMemoryPolicy=podMemoryPolicy,exports.fullGc=fullGc,exports.countLiveInstances=countLiveInstances,exports.createLiveObjectCounter=createLiveObjectCounter,exports.delayLabel=delayLabel,exports.createPodMemoryMonitor=createPodMemoryMonitor,exports.ageLabel=ageLabel,exports.describePodMemory=describePodMemory,exports.describeRetained=describeRetained,exports.describeReleased=describeReleased;const tslib_1=require("tslib"),node_module_1=require("node:module"),v8=tslib_1.__importStar(require("node:v8")),vm=tslib_1.__importStar(require("node:vm"));exports.RETAINED_MIN_GCS=2;const numberFrom=i((s,e,t)=>{if(s===void 0||s.trim()==="")return e;const r=Number(s);return Number.isFinite(r)&&r>=t?r:e},"numberFrom");function podMemoryPolicy(s=process.env){const e=["0","false","no","off"].includes((s.LENSMCP_POD_GC??"").trim().toLowerCase()),t=(s.LENSMCP_POD_GC_SCHEDULE_MS??"").split(",").map(r=>Number(r.trim())).filter(r=>Number.isFinite(r)&&r>0).map(Math.floor).sort((r,a)=>r-a);return{enabled:!e,scheduleMs:t.length?t:[3e4,12e4,3e5],idleMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_IDLE_MS,6e4,0)),idleEveryMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_IDLE_EVERY_MS,3e5,0)),retainedAfterMs:Math.floor(numberFrom(s.LENSMCP_POD_RETAINED_AFTER_MS,3e5,0)),settleMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_SETTLE_MS,2e3,0)),countMaxBytes:Math.floor(numberFrom(s.LENSMCP_POD_COUNT_MAX_MB,64,0)*1024*1024)}}d(podMemoryPolicy,"podMemoryPolicy"),i(podMemoryPolicy,"podMemoryPolicy");let collector;function fullGc(){if(collector===void 0)try{v8.setFlagsFromString("--expose-gc");const s=vm.runInNewContext("gc");collector=typeof s=="function"?s:null}catch{collector=null}if(!collector)return!1;try{return collector(),!0}catch{return!1}}d(fullGc,"fullGc"),i(fullGc,"fullGc");function countLiveInstances(s){const e=v8.queryObjects;if(typeof e!="function"||typeof s!="function")return;const t=process.emitWarning;process.emitWarning=i(function(r,...a){const l=typeof a[0]=="string"?a[0]:a[0]?.type,n=typeof r=="string"?r:r?.message;if(!(l==="ExperimentalWarning"&&/queryObjects/.test(n??"")))return t.call(process,r,...a)},"quiet");try{const r=e(s,{format:"count"});return typeof r=="number"&&Number.isFinite(r)?r:void 0}catch{return}finally{process.emitWarning=t}}d(countLiveInstances,"countLiveInstances"),i(countLiveInstances,"countLiveInstances");const LEAK_CLASSES=[{name:"NestContainer",pkg:"@nestjs/core",pick:i(s=>s.NestContainer,"pick")},{name:"ZodObject",pkg:"zod",pick:i(s=>s.ZodObject,"pick")},{name:"DataSource",pkg:"typeorm",pick:i(s=>s.DataSource,"pick")},{name:"Redis",pkg:"ioredis",pick:i(s=>typeof s=="function"?s:s.default??s.Redis,"pick")},{name:"Worker",pkg:"bullmq",pick:i(s=>s.Worker,"pick")}];function createLiveObjectCounter(s,e=require.cache){let t;try{t=(0,node_module_1.createRequire)(s)}catch{t=void 0}const r=new Map,a=i(l=>{if(!t)return;let n=r.get(l.pkg);if(n===void 0){try{n=t.resolve(l.pkg)}catch{n=null}r.set(l.pkg,n)}if(!n)return;const p=e[n]?.exports;if(!(!p||typeof p!="object"&&typeof p!="function"))try{return l.pick(p)}catch{return}},"classOf");return{containers:i(()=>countLiveInstances(a(LEAK_CLASSES[0])),"containers"),all:i(()=>{const l={};for(const n of LEAK_CLASSES){const p=countLiveInstances(a(n));p!==void 0&&(l[n.name]=p)}return Object.keys(l).length?l:void 0},"all")}}d(createLiveObjectCounter,"createLiveObjectCounter"),i(createLiveObjectCounter,"createLiveObjectCounter");class LensmcpClosedGeneration{static{d(this,"LensmcpClosedGeneration")}static{i(this,"LensmcpClosedGeneration")}constructor(e,t){this.gen=e,this.refs={},this.label=`lensmcp closed generation ${e}`;for(const[r,a]of Object.entries(t))a&&(typeof a=="object"||typeof a=="function")&&(this.refs[r]=new WeakRef(a))}}exports.LensmcpClosedGeneration=LensmcpClosedGeneration;class GenerationTracker{static{d(this,"GenerationTracker")}static{i(this,"GenerationTracker")}constructor(e={}){this.opts=e,this.closed=new Map,this.freed=new Map,this.now=e.now??Date.now,this.registry=new FinalizationRegistry(t=>this.collected(t))}close(e,t,r){if(this.closed.has(e)||this.freed.has(e))return;const a=new LensmcpClosedGeneration(e,r),l=new Set(Object.keys(a.refs));if(l.size===0){this.markFreed(e);return}this.closed.set(e,{marker:a,closedAt:this.now(),swapsAtClose:t,gcs:0,alive:l});for(const[n,p]of Object.entries(a.refs)){const m=p.deref();m?this.registry.register(m,{gen:e,name:n}):l.delete(n)}l.size===0&&this.drop(e)}noteGc(){for(const e of this.closed.values())e.gcs+=1}isAlive(e){if(this.closed.has(e))return!0;if(this.freed.has(e))return!1}views(){return[...this.closed.entries()].map(([e,t])=>this.view(e,t))}dueForReport(e,t=this.now()){return this.views().filter(r=>r.reportedAt===void 0&&r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e)}retainedCount(e,t=this.now()){return this.views().filter(r=>r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e).length}markReported(e,t=this.now()){const r=this.closed.get(e);r&&(r.reportedAt=t)}oldestRetained(e,t=this.now()){return this.views().filter(r=>r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e).sort((r,a)=>r.closedAt-a.closedAt)[0]}markers(){return[...this.closed.values()].map(e=>e.marker)}forgetAll(){const e=this.views().filter(t=>t.reportedAt!==void 0);for(const t of[...this.closed.keys()])this.drop(t);return e}collected(e){const t=this.closed.get(e.gen);if(!t||(t.alive.delete(e.name),t.alive.size>0))return;const r=this.view(e.gen,t);this.drop(e.gen);try{this.opts.onFreed?.(r,r.reportedAt!==void 0)}catch{}}drop(e){this.closed.delete(e),this.markFreed(e)}markFreed(e){this.freed.set(e,this.now()),this.freed.size>64&&this.freed.delete(this.freed.keys().next().value)}view(e,t){return{gen:e,closedAt:t.closedAt,swapsAtClose:t.swapsAtClose,gcs:t.gcs,alive:[...t.alive],...t.reportedAt!==void 0?{reportedAt:t.reportedAt}:{}}}}exports.GenerationTracker=GenerationTracker;const MB=1024*1024;function delayLabel(s){return s%6e4===0?`${s/6e4}m`:s%1e3===0?`${s/1e3}s`:`${s}ms`}d(delayLabel,"delayLabel"),i(delayLabel,"delayLabel");const turn=i(()=>new Promise(s=>setImmediate(s)),"turn");function createPodMemoryMonitor(s){const e=s.now??Date.now,t=s.policy,r=new GenerationTracker({now:e,onFreed:i((o,u)=>{u&&s.send({type:"pod-memory-released",released:{gen:o.gen,closedAt:o.closedAt,ageMs:e()-o.closedAt,reason:"collected"}})},"onFreed")});let a=Promise.resolve(),l=!1,n,p,m,w=0,A=!1,C=0;const M=i((o,u)=>{const c=setTimeout(o,Math.max(0,u));return c.unref?.(),c},"unrefTimer");function E(){return!n||n.previousGen===void 0?null:n.previousClosed()?r.isAlive(n.previousGen)!==!1:!0}d(E,"S"),i(E,"previousAlive");async function S(o){const u=process.memoryUsage().heapUsed,c=process.hrtime.bigint();fullGc();const f=Number(process.hrtime.bigint()-c)/1e6;r.noteGc(),await turn(),await turn();let h=s.containers?.();const v=process.memoryUsage(),g=v8.getHeapStatistics(),R=v8.getHeapSpaceStatistics().find(P=>P.space_name==="new_space"),$=s.state(),L=r.views(),N=e();return h!==void 0&&h<0&&(h=void 0),{trigger:o,at:N,gen:$.gen,swaps:$.swaps,live:v.heapUsed,used:u,reserved:v.heapTotal,rss:v.rss,external:v.external,youngGen:R?.space_size??0,limit:g.heap_size_limit,...$.baseline!==void 0?{baseline:$.baseline}:{},gcMs:Math.round(f),...n?.previousGen!==void 0?{previousGen:n.previousGen}:{},previousAlive:E(),closedAlive:L.length,retained:r.retainedCount(t.retainedAfterMs,N),...h!==void 0?{containers:h}:{},sourceMaps:process.sourceMapsEnabled===!0}}d(S,"k"),i(S,"collect");function G(o){for(const c of r.dueForReport(t.retainedAfterMs,o.at)){r.markReported(c.gen,o.at);const f=o.live<=t.countMaxBytes?s.counter?.all():void 0;s.send({type:"pod-memory-retained",retained:{gen:c.gen,closedAt:c.closedAt,ageMs:o.at-c.closedAt,swapsSinceClose:Math.max(0,o.swaps-c.swapsAtClose),gcs:c.gcs,alive:c.alive,...f?{counts:f}:{},live:o.live}})}const u=r.oldestRetained(t.retainedAfterMs,o.at);if(u)try{s.onRetained?.(u)}catch{}n&&!n.done&&o.previousAlive===!1&&_(),k()}d(G,"G"),i(G,"afterCollect");function y(o){const u=a.then(async()=>{const c=await S(o);return A=!1,l||(s.send({type:"pod-memory",report:c}),G(c)),c});return a=u.catch(()=>{}),u}d(y,"_"),i(y,"measure");function b(o){if(!l){if(s.state().busy){M(()=>b(o),1e3);return}y(o).catch(()=>{})}}d(b,"b"),i(b,"measureWhenFree");function _(){if(n){n.done=!0;for(const o of n.timers)clearTimeout(o);n.timers=[]}}d(_,"$"),i(_,"finishSchedule");function k(){if(p&&clearTimeout(p),p=void 0,l||!t.enabled)return;const o=r.views().filter(f=>f.reportedAt===void 0);if(o.length===0)return;const u=e(),c=Math.min(...o.map(f=>Math.max(f.closedAt+t.retainedAfterMs,u+1e3)));p=M(()=>{p=void 0,b("retention-check")},c-u)}d(k,"C"),i(k,"armRetentionCheck");function x(){if(l||!t.enabled||t.idleMs<=0||m)return;const o=Math.max(t.idleMs-(e()-w),C+t.idleEveryMs-e(),250);m=M(()=>{if(m=void 0,!!A){if(e()-w<t.idleMs||e()-C<t.idleEveryMs||s.state().busy){x();return}C=e(),b("idle")}},o)}return d(x,"x"),i(x,"armIdle"),{tracker:r,booted:i(()=>y("boot"),"booted"),swapped(o,u){if(l||(_(),w=e(),A=!0,!t.enabled))return;const c={...o!==void 0?{previousGen:o}:{},previousClosed:u,timers:[],done:!1};n=c;const f=e(),h=t.scheduleMs[0]??Number.POSITIVE_INFINITY,v=i(()=>{if(c.done||n!==c)return;const g=s.state();if(!g.busy&&u()&&e()-g.lastBusyAt>=t.settleMs){y("swap-settled").catch(()=>{});return}e()-f+500<h&&c.timers.push(M(v,500))},"settledCheck");c.timers.push(M(v,Math.min(t.settleMs,500)));for(const g of t.scheduleMs)c.timers.push(M(()=>{c.done||n!==c||b(`swap+${delayLabel(g)}`)},g))},closed(o,u,c){r.close(o,u,c),k()},activity(){w=e(),A=!0,x()},measure:y,oldestRetained:i(()=>r.oldestRetained(t.retainedAfterMs),"oldestRetained"),stop(){l=!0,_(),p&&clearTimeout(p),m&&clearTimeout(m)}}}d(createPodMemoryMonitor,"createPodMemoryMonitor"),i(createPodMemoryMonitor,"createPodMemoryMonitor");const mbOf=i(s=>s===void 0?"?":`${Math.round(s/MB)} MB`,"mbOf");function ageLabel(s){return s<9e4?`${Math.max(0,Math.round(s/1e3))}s`:`${Math.round(s/6e4)} min`}d(ageLabel,"ageLabel"),i(ageLabel,"ageLabel");function describePodMemory(s,e){const t=e.trigger==="boot"?`${s} memory at boot: ${mbOf(e.live)} live after a full GC \u2014 the baseline later readings compare with`:`${s} memory (${e.trigger}): ${mbOf(e.live)} live after a full GC`+(e.used-e.live>=MB?` (${mbOf(e.used)} before it)`:"")+(e.baseline?`, ${(e.live/e.baseline).toFixed(1)}\xD7 its ${mbOf(e.baseline)} boot baseline`:""),r=[`${mbOf(e.reserved)} reserved`,`rss ${mbOf(e.rss)}`,`generation ${e.gen}, ${e.swaps} swap${e.swaps===1?"":"s"}`,`GC ${e.gcMs} ms`];return e.previousAlive===!1?r.push(`generation ${e.previousGen} freed`):e.previousAlive===!0&&r.push(`generation ${e.previousGen} still in memory`),e.containers!==void 0&&r.push(`${e.containers} Nest container${e.containers===1?"":"s"}`),e.retained>0&&r.push(`${e.retained} closed generation${e.retained===1?"":"s"} RETAINED`),`${t} \xB7 ${r.join(" \xB7 ")}`}d(describePodMemory,"describePodMemory"),i(describePodMemory,"describePodMemory");function describeRetained(s,e,t){const r=t.counts&&Object.keys(t.counts).length?` Live now: ${Object.entries(t.counts).map(([a,l])=>`${a} ${l}`).join(", ")}.`:"";return`${s} ${e}: generation ${t.gen} still in memory ${ageLabel(t.ageMs)} after its app was closed (${t.swapsSinceClose} swap${t.swapsSinceClose===1?"":"s"} since, ${t.gcs} full GCs; still alive: ${t.alive.join(", ")}) \u2014 something still points into it.${r} See what with \`lensmcp mem ${e} --snapshot --analyze\`.`}d(describeRetained,"describeRetained"),i(describeRetained,"describeRetained");function describeReleased(s,e,t){return t.reason==="pod-exited"?`${s} ${e}: generation ${t.gen} went with its pod`:`${s} ${e}: generation ${t.gen} was collected after all, ${ageLabel(t.ageMs)} after its app was closed`}d(describeReleased,"describeReleased"),i(describeReleased,"describeReleased");
|
|
1
|
+
"use strict";var O=Object.defineProperty;var d=(s,e)=>O(s,"name",{value:e,configurable:!0});var I=Object.defineProperty,i=d((s,e)=>I(s,"name",{value:e,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GenerationTracker=exports.LensmcpClosedGeneration=exports.RETAINED_MIN_GCS=void 0,exports.podMemoryPolicy=podMemoryPolicy,exports.fullGc=fullGc,exports.countLiveInstances=countLiveInstances,exports.createLiveObjectCounter=createLiveObjectCounter,exports.delayLabel=delayLabel,exports.createPodMemoryMonitor=createPodMemoryMonitor,exports.ageLabel=ageLabel,exports.describePodMemory=describePodMemory,exports.describeRetained=describeRetained,exports.describeReleased=describeReleased;const tslib_1=require("tslib"),node_module_1=require("node:module"),v8=tslib_1.__importStar(require("node:v8")),vm=tslib_1.__importStar(require("node:vm"));exports.RETAINED_MIN_GCS=2;const numberFrom=i((s,e,t)=>{if(s===void 0||s.trim()==="")return e;const r=Number(s);return Number.isFinite(r)&&r>=t?r:e},"numberFrom");function podMemoryPolicy(s=process.env){const e=["0","false","no","off"].includes((s.LENSMCP_POD_GC??"").trim().toLowerCase()),t=(s.LENSMCP_POD_GC_SCHEDULE_MS??"").split(",").map(r=>Number(r.trim())).filter(r=>Number.isFinite(r)&&r>0).map(Math.floor).sort((r,a)=>r-a);return{enabled:!e,scheduleMs:t.length?t:[3e4,12e4,3e5],idleMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_IDLE_MS,6e4,0)),idleEveryMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_IDLE_EVERY_MS,3e5,0)),retainedAfterMs:Math.floor(numberFrom(s.LENSMCP_POD_RETAINED_AFTER_MS,3e5,0)),settleMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_SETTLE_MS,2e3,0)),countMaxBytes:Math.floor(numberFrom(s.LENSMCP_POD_COUNT_MAX_MB,64,0)*1024*1024)}}d(podMemoryPolicy,"podMemoryPolicy"),i(podMemoryPolicy,"podMemoryPolicy");let collector;function fullGc(){if(collector===void 0)try{v8.setFlagsFromString("--expose-gc");const s=vm.runInNewContext("gc");collector=typeof s=="function"?s:null}catch{collector=null}if(!collector)return!1;try{return collector(),!0}catch{return!1}}d(fullGc,"fullGc"),i(fullGc,"fullGc");function countLiveInstances(s){const e=v8.queryObjects;if(typeof e!="function"||typeof s!="function")return;const t=process.emitWarning;process.emitWarning=i(function(r,...a){const l=typeof a[0]=="string"?a[0]:a[0]?.type,n=typeof r=="string"?r:r?.message;if(!(l==="ExperimentalWarning"&&/queryObjects/.test(n??"")))return t.call(process,r,...a)},"quiet");try{const r=e(s,{format:"count"});return typeof r=="number"&&Number.isFinite(r)?r:void 0}catch{return}finally{process.emitWarning=t}}d(countLiveInstances,"countLiveInstances"),i(countLiveInstances,"countLiveInstances");const LEAK_CLASSES=[{name:"NestContainer",pkg:"@nestjs/core",pick:i(s=>s.NestContainer,"pick")},{name:"ZodObject",pkg:"zod",pick:i(s=>s.ZodObject,"pick")},{name:"DataSource",pkg:"typeorm",pick:i(s=>s.DataSource,"pick")},{name:"Redis",pkg:"ioredis",pick:i(s=>typeof s=="function"?s:s.default??s.Redis,"pick")},{name:"Worker",pkg:"bullmq",pick:i(s=>s.Worker,"pick")}];function createLiveObjectCounter(s,e=require.cache){let t;try{t=(0,node_module_1.createRequire)(s)}catch{t=void 0}const r=new Map,a=i(l=>{if(!t)return;let n=r.get(l.pkg);if(n===void 0){try{n=t.resolve(l.pkg)}catch{n=null}r.set(l.pkg,n)}if(!n)return;const p=e[n]?.exports;if(!(!p||typeof p!="object"&&typeof p!="function"))try{return l.pick(p)}catch{return}},"classOf");return{containers:i(()=>countLiveInstances(a(LEAK_CLASSES[0])),"containers"),all:i(()=>{const l={};for(const n of LEAK_CLASSES){const p=countLiveInstances(a(n));p!==void 0&&(l[n.name]=p)}return Object.keys(l).length?l:void 0},"all")}}d(createLiveObjectCounter,"createLiveObjectCounter"),i(createLiveObjectCounter,"createLiveObjectCounter");class LensmcpClosedGeneration{static{d(this,"LensmcpClosedGeneration")}static{i(this,"LensmcpClosedGeneration")}constructor(e,t){this.gen=e,this.refs={},this.label=`lensmcp closed generation ${e}`;for(const[r,a]of Object.entries(t))a&&(typeof a=="object"||typeof a=="function")&&(this.refs[r]=new WeakRef(a))}}exports.LensmcpClosedGeneration=LensmcpClosedGeneration;class GenerationTracker{static{d(this,"GenerationTracker")}static{i(this,"GenerationTracker")}constructor(e={}){this.opts=e,this.closed=new Map,this.freed=new Map,this.now=e.now??Date.now,this.registry=new FinalizationRegistry(t=>this.collected(t))}close(e,t,r){if(this.closed.has(e)||this.freed.has(e))return;const a=new LensmcpClosedGeneration(e,r),l=new Set(Object.keys(a.refs));if(l.size===0){this.markFreed(e);return}this.closed.set(e,{marker:a,closedAt:this.now(),swapsAtClose:t,gcs:0,alive:l});for(const[n,p]of Object.entries(a.refs)){const m=p.deref();m?this.registry.register(m,{gen:e,name:n}):l.delete(n)}l.size===0&&this.drop(e)}noteGc(){for(const e of this.closed.values())e.gcs+=1}isAlive(e){if(this.closed.has(e))return!0;if(this.freed.has(e))return!1}views(){return[...this.closed.entries()].map(([e,t])=>this.view(e,t))}dueForReport(e,t=this.now()){return this.views().filter(r=>r.reportedAt===void 0&&r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e)}retainedCount(e,t=this.now()){return this.views().filter(r=>r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e).length}markReported(e,t=this.now()){const r=this.closed.get(e);r&&(r.reportedAt=t)}oldestRetained(e,t=this.now()){return this.views().filter(r=>r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e).sort((r,a)=>r.closedAt-a.closedAt)[0]}markers(){return[...this.closed.values()].map(e=>e.marker)}forgetAll(){const e=this.views().filter(t=>t.reportedAt!==void 0);for(const t of[...this.closed.keys()])this.drop(t);return e}collected(e){const t=this.closed.get(e.gen);if(!t||(t.alive.delete(e.name),t.alive.size>0))return;const r=this.view(e.gen,t);this.drop(e.gen);try{this.opts.onFreed?.(r,r.reportedAt!==void 0)}catch{}}drop(e){this.closed.delete(e),this.markFreed(e)}markFreed(e){this.freed.set(e,this.now()),this.freed.size>64&&this.freed.delete(this.freed.keys().next().value)}view(e,t){return{gen:e,closedAt:t.closedAt,swapsAtClose:t.swapsAtClose,gcs:t.gcs,alive:[...t.alive],...t.reportedAt!==void 0?{reportedAt:t.reportedAt}:{}}}}exports.GenerationTracker=GenerationTracker;const MB=1024*1024;function delayLabel(s){return s%6e4===0?`${s/6e4}m`:s%1e3===0?`${s/1e3}s`:`${s}ms`}d(delayLabel,"delayLabel"),i(delayLabel,"delayLabel");const turn=i(()=>new Promise(s=>setImmediate(s)),"turn");function createPodMemoryMonitor(s){const e=s.now??Date.now,t=s.policy,r=new GenerationTracker({now:e,onFreed:i((o,u)=>{u&&s.send({type:"pod-memory-released",released:{gen:o.gen,closedAt:o.closedAt,ageMs:e()-o.closedAt,reason:"collected"}})},"onFreed")});let a=Promise.resolve(),l=!1,n,p,m,w=0,A=!1,C=0;const M=i((o,u)=>{const c=setTimeout(o,Math.max(0,u));return c.unref?.(),c},"unrefTimer");function E(){return!n||n.previousGen===void 0?null:n.previousClosed()?r.isAlive(n.previousGen)!==!1:!0}d(E,"S"),i(E,"previousAlive");async function S(o){const u=process.memoryUsage().heapUsed,c=process.hrtime.bigint();(s.gc??fullGc)();const f=Number(process.hrtime.bigint()-c)/1e6;r.noteGc(),await turn(),await turn();let h=s.containers?.();const v=process.memoryUsage(),g=v8.getHeapStatistics(),R=v8.getHeapSpaceStatistics().find(P=>P.space_name==="new_space"),$=s.state(),L=r.views(),N=e();return h!==void 0&&h<0&&(h=void 0),{trigger:o,at:N,gen:$.gen,swaps:$.swaps,live:v.heapUsed,used:u,reserved:v.heapTotal,rss:v.rss,external:v.external,youngGen:R?.space_size??0,limit:g.heap_size_limit,...$.baseline!==void 0?{baseline:$.baseline}:{},gcMs:Math.round(f),...n?.previousGen!==void 0?{previousGen:n.previousGen}:{},previousAlive:E(),closedAlive:L.length,retained:r.retainedCount(t.retainedAfterMs,N),...h!==void 0?{containers:h}:{},sourceMaps:process.sourceMapsEnabled===!0}}d(S,"k"),i(S,"collect");function G(o){for(const c of r.dueForReport(t.retainedAfterMs,o.at)){r.markReported(c.gen,o.at);const f=o.live<=t.countMaxBytes?s.counter?.all():void 0;s.send({type:"pod-memory-retained",retained:{gen:c.gen,closedAt:c.closedAt,ageMs:o.at-c.closedAt,swapsSinceClose:Math.max(0,o.swaps-c.swapsAtClose),gcs:c.gcs,alive:c.alive,...f?{counts:f}:{},live:o.live}})}const u=r.oldestRetained(t.retainedAfterMs,o.at);if(u)try{s.onRetained?.(u)}catch{}n&&!n.done&&o.previousAlive===!1&&_(),k()}d(G,"G"),i(G,"afterCollect");function y(o){const u=a.then(async()=>{const c=await S(o);return A=!1,l||(s.send({type:"pod-memory",report:c}),G(c)),c});return a=u.catch(()=>{}),u}d(y,"_"),i(y,"measure");function b(o){if(!l){if(s.state().busy){M(()=>b(o),1e3);return}y(o).catch(()=>{})}}d(b,"b"),i(b,"measureWhenFree");function _(){if(n){n.done=!0;for(const o of n.timers)clearTimeout(o);n.timers=[]}}d(_,"$"),i(_,"finishSchedule");function k(){if(p&&clearTimeout(p),p=void 0,l||!t.enabled)return;const o=r.views().filter(f=>f.reportedAt===void 0);if(o.length===0)return;const u=e(),c=Math.min(...o.map(f=>Math.max(f.closedAt+t.retainedAfterMs,u+1e3)));p=M(()=>{p=void 0,b("retention-check")},c-u)}d(k,"C"),i(k,"armRetentionCheck");function x(){if(l||!t.enabled||t.idleMs<=0||m)return;const o=Math.max(t.idleMs-(e()-w),C+t.idleEveryMs-e(),250);m=M(()=>{if(m=void 0,!!A){if(e()-w<t.idleMs||e()-C<t.idleEveryMs||s.state().busy){x();return}C=e(),b("idle")}},o)}return d(x,"x"),i(x,"armIdle"),{tracker:r,booted:i(()=>y("boot"),"booted"),swapped(o,u){if(l||(_(),w=e(),A=!0,!t.enabled))return;const c={...o!==void 0?{previousGen:o}:{},previousClosed:u,timers:[],done:!1};n=c;const f=e(),h=t.scheduleMs[0]??Number.POSITIVE_INFINITY,v=i(()=>{if(c.done||n!==c)return;const g=s.state();if(!g.busy&&u()&&e()-g.lastBusyAt>=t.settleMs){y("swap-settled").catch(()=>{});return}e()-f+500<h&&c.timers.push(M(v,500))},"settledCheck");c.timers.push(M(v,Math.min(t.settleMs,500)));for(const g of t.scheduleMs)c.timers.push(M(()=>{c.done||n!==c||b(`swap+${delayLabel(g)}`)},g))},closed(o,u,c){r.close(o,u,c),k()},activity(){w=e(),A=!0,x()},measure:y,oldestRetained:i(()=>r.oldestRetained(t.retainedAfterMs),"oldestRetained"),stop(){l=!0,_(),p&&clearTimeout(p),m&&clearTimeout(m)}}}d(createPodMemoryMonitor,"createPodMemoryMonitor"),i(createPodMemoryMonitor,"createPodMemoryMonitor");const mbOf=i(s=>s===void 0?"?":`${Math.round(s/MB)} MB`,"mbOf");function ageLabel(s){return s<9e4?`${Math.max(0,Math.round(s/1e3))}s`:`${Math.round(s/6e4)} min`}d(ageLabel,"ageLabel"),i(ageLabel,"ageLabel");function describePodMemory(s,e){const t=e.trigger==="boot"?`${s} memory at boot: ${mbOf(e.live)} live after a full GC \u2014 the baseline later readings compare with`:`${s} memory (${e.trigger}): ${mbOf(e.live)} live after a full GC`+(e.used-e.live>=MB?` (${mbOf(e.used)} before it)`:"")+(e.baseline?`, ${(e.live/e.baseline).toFixed(1)}\xD7 its ${mbOf(e.baseline)} boot baseline`:""),r=[`${mbOf(e.reserved)} reserved`,`rss ${mbOf(e.rss)}`,`generation ${e.gen}, ${e.swaps} swap${e.swaps===1?"":"s"}`,`GC ${e.gcMs} ms`];return e.previousAlive===!1?r.push(`generation ${e.previousGen} freed`):e.previousAlive===!0&&r.push(`generation ${e.previousGen} still in memory`),e.containers!==void 0&&r.push(`${e.containers} Nest container${e.containers===1?"":"s"}`),e.retained>0&&r.push(`${e.retained} closed generation${e.retained===1?"":"s"} RETAINED`),`${t} \xB7 ${r.join(" \xB7 ")}`}d(describePodMemory,"describePodMemory"),i(describePodMemory,"describePodMemory");function describeRetained(s,e,t){const r=t.counts&&Object.keys(t.counts).length?` Live now: ${Object.entries(t.counts).map(([a,l])=>`${a} ${l}`).join(", ")}.`:"";return`${s} ${e}: generation ${t.gen} still in memory ${ageLabel(t.ageMs)} after its app was closed (${t.swapsSinceClose} swap${t.swapsSinceClose===1?"":"s"} since, ${t.gcs} full GCs; still alive: ${t.alive.join(", ")}) \u2014 something still points into it.${r} See what with \`lensmcp mem ${e} --snapshot --analyze\`.`}d(describeRetained,"describeRetained"),i(describeRetained,"describeRetained");function describeReleased(s,e,t){return t.reason==="pod-exited"?`${s} ${e}: generation ${t.gen} went with its pod`:`${s} ${e}: generation ${t.gen} was collected after all, ${ageLabel(t.ageMs)} after its app was closed`}d(describeReleased,"describeReleased"),i(describeReleased,"describeReleased");
|