Skip to content

Commit a3fe162

Browse files
committed
fix: add timeout configuration to Ollama fetcher for remote connections
- Add 10-second timeout to axios requests in getOllamaModels() - Improve error handling for timeout scenarios with specific warning message - Update tests to verify timeout configuration is passed correctly - Add new test case for timeout error handling Fixes #5677: Ollama remote server connections now timeout gracefully instead of hanging indefinitely
1 parent 62f97b9 commit a3fe162

File tree

2 files changed

+43
-10
lines changed

2 files changed

+43
-10
lines changed

src/api/providers/fetchers/__tests__/ollama.test.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,14 @@ describe("Ollama Fetcher", () => {
109109
const result = await getOllamaModels(baseUrl)
110110

111111
expect(mockedAxios.get).toHaveBeenCalledTimes(1)
112-
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`)
112+
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`, { timeout: 10000 })
113113

114114
expect(mockedAxios.post).toHaveBeenCalledTimes(1)
115-
expect(mockedAxios.post).toHaveBeenCalledWith(`${baseUrl}/api/show`, { model: modelName })
115+
expect(mockedAxios.post).toHaveBeenCalledWith(
116+
`${baseUrl}/api/show`,
117+
{ model: modelName },
118+
{ timeout: 10000 },
119+
)
116120

117121
expect(typeof result).toBe("object")
118122
expect(result).not.toBeInstanceOf(Array)
@@ -131,7 +135,7 @@ describe("Ollama Fetcher", () => {
131135
const result = await getOllamaModels(baseUrl)
132136

133137
expect(mockedAxios.get).toHaveBeenCalledTimes(1)
134-
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`)
138+
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`, { timeout: 10000 })
135139
expect(mockedAxios.post).not.toHaveBeenCalled()
136140
expect(result).toEqual({})
137141
})
@@ -147,14 +151,33 @@ describe("Ollama Fetcher", () => {
147151
const result = await getOllamaModels(baseUrl)
148152

149153
expect(mockedAxios.get).toHaveBeenCalledTimes(1)
150-
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`)
154+
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`, { timeout: 10000 })
151155
expect(mockedAxios.post).not.toHaveBeenCalled()
152156
expect(consoleInfoSpy).toHaveBeenCalledWith(`Failed connecting to Ollama at ${baseUrl}`)
153157
expect(result).toEqual({})
154158

155159
consoleInfoSpy.mockRestore() // Restore original console.info
156160
})
157161

162+
it("should log a warning message and return an empty object on timeout", async () => {
163+
const baseUrl = "http://localhost:11434"
164+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}) // Spy and suppress output
165+
166+
const timeoutError = new Error("timeout of 10000ms exceeded") as any
167+
timeoutError.code = "ECONNABORTED"
168+
mockedAxios.get.mockRejectedValueOnce(timeoutError)
169+
170+
const result = await getOllamaModels(baseUrl)
171+
172+
expect(mockedAxios.get).toHaveBeenCalledTimes(1)
173+
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`, { timeout: 10000 })
174+
expect(mockedAxios.post).not.toHaveBeenCalled()
175+
expect(consoleWarnSpy).toHaveBeenCalledWith(`Connection to Ollama at ${baseUrl} timed out after 10 seconds`)
176+
expect(result).toEqual({})
177+
178+
consoleWarnSpy.mockRestore() // Restore original console.warn
179+
})
180+
158181
it("should handle models with null families field in API response", async () => {
159182
const baseUrl = "http://localhost:11434"
160183
const modelName = "test-model:latest"
@@ -205,10 +228,14 @@ describe("Ollama Fetcher", () => {
205228
const result = await getOllamaModels(baseUrl)
206229

207230
expect(mockedAxios.get).toHaveBeenCalledTimes(1)
208-
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`)
231+
expect(mockedAxios.get).toHaveBeenCalledWith(`${baseUrl}/api/tags`, { timeout: 10000 })
209232

210233
expect(mockedAxios.post).toHaveBeenCalledTimes(1)
211-
expect(mockedAxios.post).toHaveBeenCalledWith(`${baseUrl}/api/show`, { model: modelName })
234+
expect(mockedAxios.post).toHaveBeenCalledWith(
235+
`${baseUrl}/api/show`,
236+
{ model: modelName },
237+
{ timeout: 10000 },
238+
)
212239

213240
expect(typeof result).toBe("object")
214241
expect(result).not.toBeInstanceOf(Array)

src/api/providers/fetchers/ollama.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,21 @@ export async function getOllamaModels(baseUrl = "http://localhost:11434"): Promi
6565
return models
6666
}
6767

68-
const response = await axios.get<OllamaModelsResponse>(`${baseUrl}/api/tags`)
68+
const response = await axios.get<OllamaModelsResponse>(`${baseUrl}/api/tags`, { timeout: 10000 })
6969
const parsedResponse = OllamaModelsResponseSchema.safeParse(response.data)
7070
let modelInfoPromises = []
7171

7272
if (parsedResponse.success) {
7373
for (const ollamaModel of parsedResponse.data.models) {
7474
modelInfoPromises.push(
7575
axios
76-
.post<OllamaModelInfoResponse>(`${baseUrl}/api/show`, {
77-
model: ollamaModel.model,
78-
})
76+
.post<OllamaModelInfoResponse>(
77+
`${baseUrl}/api/show`,
78+
{
79+
model: ollamaModel.model,
80+
},
81+
{ timeout: 10000 },
82+
)
7983
.then((ollamaModelInfo) => {
8084
models[ollamaModel.name] = parseOllamaModel(ollamaModelInfo.data)
8185
}),
@@ -89,6 +93,8 @@ export async function getOllamaModels(baseUrl = "http://localhost:11434"): Promi
8993
} catch (error) {
9094
if (error.code === "ECONNREFUSED") {
9195
console.warn(`Failed connecting to Ollama at ${baseUrl}`)
96+
} else if (error.code === "ECONNABORTED" || error.message?.includes("timeout")) {
97+
console.warn(`Connection to Ollama at ${baseUrl} timed out after 10 seconds`)
9298
} else {
9399
console.error(
94100
`Error fetching Ollama models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,

0 commit comments

Comments
 (0)