Skip to content

Commit 1ff8569

Browse files
committed
fix(components/execd): delete context from memcache when DeleteContext succeeds
1 parent 4d79b3c commit 1ff8569

File tree

2 files changed

+108
-12
lines changed

2 files changed

+108
-12
lines changed

components/execd/pkg/runtime/context.go

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,7 @@ func (c *Controller) CreateContext(req *CreateContextRequest) (string, error) {
6565
}
6666

6767
func (c *Controller) DeleteContext(session string) error {
68-
kernel := c.getJupyterKernel(session)
69-
if kernel == nil {
70-
return ErrContextNotFound
71-
}
72-
73-
c.mu.Lock()
74-
defer c.mu.Unlock()
75-
76-
return c.jupyterClient().DeleteSession(session)
68+
return c.deleteSessionAndCleanup(session)
7769
}
7870

7971
func (c *Controller) ListContext(language string) ([]CodeContext, error) {
@@ -93,16 +85,41 @@ func (c *Controller) DeleteLanguageContext(language Language) error {
9385
return err
9486
}
9587

96-
client := c.jupyterClient()
88+
seen := make(map[string]struct{})
9789
for _, context := range contexts {
98-
err := client.DeleteSession(context.ID)
99-
if err != nil {
90+
if _, ok := seen[context.ID]; ok {
91+
continue
92+
}
93+
seen[context.ID] = struct{}{}
94+
95+
if err := c.deleteSessionAndCleanup(context.ID); err != nil {
10096
return fmt.Errorf("error deleting context %s: %w", context.ID, err)
10197
}
10298
}
10399
return nil
104100
}
105101

102+
func (c *Controller) deleteSessionAndCleanup(session string) error {
103+
if c.getJupyterKernel(session) == nil {
104+
return ErrContextNotFound
105+
}
106+
107+
if err := c.jupyterClient().DeleteSession(session); err != nil {
108+
return err
109+
}
110+
111+
c.mu.Lock()
112+
defer c.mu.Unlock()
113+
114+
delete(c.jupyterClientMap, session)
115+
for lang, id := range c.defaultLanguageJupyterSessions {
116+
if id == session {
117+
delete(c.defaultLanguageJupyterSessions, lang)
118+
}
119+
}
120+
return nil
121+
}
122+
106123
func (c *Controller) newContextID() string {
107124
return strings.ReplaceAll(uuid.New().String(), "-", "")
108125
}

components/execd/pkg/runtime/context_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ package runtime
1616

1717
import (
1818
"errors"
19+
"net/http"
20+
"net/http/httptest"
1921
"os"
2022
"path/filepath"
23+
"strings"
2124
"testing"
2225
)
2326

@@ -108,3 +111,79 @@ func TestDeleteContext_NotFound(t *testing.T) {
108111
t.Fatalf("unexpected error: %v", err)
109112
}
110113
}
114+
115+
func TestDeleteContext_RemovesCacheOnSuccess(t *testing.T) {
116+
sessionID := "sess-123"
117+
118+
// mock jupyter server that accepts DELETE
119+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
120+
if r.Method != http.MethodDelete {
121+
t.Fatalf("unexpected method: %s", r.Method)
122+
}
123+
if !strings.HasSuffix(r.URL.Path, "/api/sessions/"+sessionID) {
124+
t.Fatalf("unexpected path: %s", r.URL.Path)
125+
}
126+
w.WriteHeader(http.StatusNoContent)
127+
}))
128+
defer server.Close()
129+
130+
c := NewController(server.URL, "token")
131+
c.jupyterClientMap[sessionID] = &jupyterKernel{language: Python}
132+
c.defaultLanguageJupyterSessions[Python] = sessionID
133+
134+
if err := c.DeleteContext(sessionID); err != nil {
135+
t.Fatalf("DeleteContext returned error: %v", err)
136+
}
137+
138+
if kernel := c.getJupyterKernel(sessionID); kernel != nil {
139+
t.Fatalf("expected cache to be cleared, found: %+v", kernel)
140+
}
141+
if _, ok := c.defaultLanguageJupyterSessions[Python]; ok {
142+
t.Fatalf("expected default session entry to be removed")
143+
}
144+
}
145+
146+
func TestDeleteLanguageContext_RemovesCacheOnSuccess(t *testing.T) {
147+
lang := Python
148+
session1 := "sess-1"
149+
session2 := "sess-2"
150+
151+
// mock jupyter server to accept two deletes
152+
deleteCalls := make(map[string]int)
153+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
154+
if r.Method != http.MethodDelete {
155+
t.Fatalf("unexpected method: %s", r.Method)
156+
}
157+
if strings.Contains(r.URL.Path, session1) {
158+
deleteCalls[session1]++
159+
} else if strings.Contains(r.URL.Path, session2) {
160+
deleteCalls[session2]++
161+
} else {
162+
t.Fatalf("unexpected path: %s", r.URL.Path)
163+
}
164+
w.WriteHeader(http.StatusNoContent)
165+
}))
166+
defer server.Close()
167+
168+
c := NewController(server.URL, "token")
169+
c.jupyterClientMap[session1] = &jupyterKernel{language: lang}
170+
c.jupyterClientMap[session2] = &jupyterKernel{language: lang}
171+
c.defaultLanguageJupyterSessions[lang] = session2
172+
173+
if err := c.DeleteLanguageContext(lang); err != nil {
174+
t.Fatalf("DeleteLanguageContext returned error: %v", err)
175+
}
176+
177+
if _, ok := c.jupyterClientMap[session1]; ok {
178+
t.Fatalf("expected session1 removed from cache")
179+
}
180+
if _, ok := c.jupyterClientMap[session2]; ok {
181+
t.Fatalf("expected session2 removed from cache")
182+
}
183+
if _, ok := c.defaultLanguageJupyterSessions[lang]; ok {
184+
t.Fatalf("expected default entry removed")
185+
}
186+
if deleteCalls[session1] != 1 || deleteCalls[session2] != 1 {
187+
t.Fatalf("unexpected delete calls: %+v", deleteCalls)
188+
}
189+
}

0 commit comments

Comments
 (0)