Skip to content

Commit 335b87f

Browse files
Fix asyncpg event loop bug with startup wrapper monkey-patch
StackApp.__init__() runs Stack.initialize() in a temporary event loop via ThreadPoolExecutor + asyncio.run(). Any asyncpg connections created during init get bound to this temp loop, causing the first request on uvicorn's real event loop to fail with: RuntimeError: Task got Future attached to a different loop The upstream fix (llama-stack PR #5837) does two things: 1. Sets async_session = None after init (via reset_engine()) 2. Adds lazy recreation logic inside SqlAlchemySqlStoreImpl that detects async_session is None and recreates the engine + session on the current event loop The current container image has neither of these changes. Setting async_session to None alone crashes with TypeError because the class has no lazy recreation code to handle the None case. The SessionMaker class is essentially that lazy recreation logic implemented externally — on the first call it recreates the engine on the correct event loop, then replaces itself with the real session maker. Remove this workaround when the container image includes PR #5837.
1 parent e2975e3 commit 335b87f

6 files changed

Lines changed: 68 additions & 1 deletion

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env python3
2+
# Workaround for asyncpg event loop bug (llama-stack #5978).
3+
# Monkey-patches StackApp to reset SQL engines after temp event loop init.
4+
# Remove when the container image includes the upstream fix (PR #5837).
5+
6+
import gc
7+
import sys
8+
9+
import llama_stack.core.server.server as server_mod
10+
from llama_stack.core.storage.sqlstore.sqlalchemy_sqlstore import SqlAlchemySqlStoreImpl
11+
from sqlalchemy.ext.asyncio import async_sessionmaker
12+
13+
_orig_init = server_mod.StackApp.__init__
14+
15+
16+
class SessionMaker:
17+
"""Recreates the SQLAlchemy engine and session on the current event loop."""
18+
19+
def __init__(self, store):
20+
self._store = store
21+
self._maker = None
22+
23+
def __call__(self):
24+
if self._maker is None:
25+
self._store._engine = self._store.create_engine()
26+
self._maker = async_sessionmaker(self._store._engine)
27+
self._store.async_session = self._maker
28+
return self._maker()
29+
30+
31+
def _patched_init(self, config, *args, **kwargs):
32+
_orig_init(self, config, *args, **kwargs)
33+
for obj in gc.get_objects():
34+
if isinstance(obj, SqlAlchemySqlStoreImpl):
35+
obj._engine = None
36+
obj.async_session = SessionMaker(obj)
37+
38+
39+
server_mod.StackApp.__init__ = _patched_init
40+
41+
from llama_stack.cli.llama import main # noqa: E402
42+
43+
sys.argv[0] = "llama"
44+
main()

internal/controller/constants.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ const (
235235
// script is stored in the ConfigMap containing vector database init scripts.
236236
VectorDBBuildScriptKey = "vector_database_build.py"
237237

238+
// LlamaStartupWrapperKey is the ConfigMap key for the startup wrapper script
239+
// that monkey-patches the asyncpg event loop bug fix. Remove with PR #5837 backport.
240+
LlamaStartupWrapperKey = "llama_startup_wrapper.py" // #nosec G101 -- ConfigMap key, not a credential
241+
238242
// Resource Version Annotation
239243
// These constants define annotation keys used to track the resource versions of specific ConfigMaps.
240244
// By recording the resource version of a ConfigMap in a Deployment, StatefulSet, or similar resource,
@@ -350,6 +354,13 @@ var vectorDatabaseCollectScript string
350354
//go:embed assets/vector_database_build.py
351355
var vectorDatabaseBuildScript string
352356

357+
// llamaStartupWrapperScript is a Python monkey-patch that fixes the asyncpg
358+
// event loop bug (ogx-ai/ogx#5978) by resetting SQL engines after StackApp
359+
// initialization. Remove when the container image includes upstream PR #5837.
360+
//
361+
//go:embed assets/llama_startup_wrapper.py
362+
var llamaStartupWrapperScript string
363+
353364
//go:embed assets/console_nginx.conf.tmpl
354365
var consoleNginxConfigTemplate string
355366

internal/controller/lcore_deployment.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,16 @@ func buildLCorePodTemplateSpec(h *common_helper.Helper, ctx context.Context, ins
6363
llamaStackMounts := []corev1.VolumeMount{}
6464
llamaStackMounts = append(llamaStackMounts, sharedMounts...)
6565
llamaStackMounts = append(llamaStackMounts, llamaCacheMounts...)
66+
llamaStackMounts = append(llamaStackMounts, corev1.VolumeMount{
67+
Name: VectorDBScriptsVolumeName,
68+
MountPath: VectorDBScriptsMountPath,
69+
ReadOnly: true,
70+
})
6671

6772
llamaStackContainer := corev1.Container{
6873
Name: "llama-stack",
6974
Image: apiv1beta1.OpenStackLightspeedDefaultValues.LCoreImageURL,
70-
Command: []string{"llama", "stack", "run", VectorDBVolumeOGXConfigPath},
75+
Command: []string{"python3", VectorDBScriptsMountPath + "/" + LlamaStartupWrapperKey, "stack", "run", VectorDBVolumeOGXConfigPath},
7176
Ports: []corev1.ContainerPort{{Name: "llama-stack", ContainerPort: LlamaStackContainerPort}},
7277
VolumeMounts: llamaStackMounts,
7378
Env: llamaEnvVars,

internal/controller/lcore_reconciler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ func reconcileVectorDBScriptsConfigMap(h *common_helper.Helper, ctx context.Cont
297297
cm.Data = map[string]string{
298298
VectorDBCollectScriptKey: vectorDatabaseCollectScript,
299299
VectorDBBuildScriptKey: vectorDatabaseBuildScript,
300+
LlamaStartupWrapperKey: llamaStartupWrapperScript,
300301
}
301302

302303
return controllerutil.SetControllerReference(h.GetBeforeObject(), cm, h.GetScheme())

test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ spec:
256256
mountPath: /vector-db-discovered-values
257257
- name: llama-cache
258258
mountPath: /tmp/llama-stack
259+
- name: vector-db-scripts
260+
mountPath: /scripts
261+
readOnly: true
259262
- name: lightspeed-service-api
260263
resources:
261264
requests:

test/kuttl/tests/update-openstacklightspeed/08-assert-openstacklightspeed-update.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ spec:
138138
mountPath: /vector-db-discovered-values
139139
- name: llama-cache
140140
mountPath: /tmp/llama-stack
141+
- name: vector-db-scripts
142+
mountPath: /scripts
143+
readOnly: true
141144
- name: lightspeed-service-api
142145
resources:
143146
requests:

0 commit comments

Comments
 (0)