Skip to content

Commit 9cf6b90

Browse files
authored
fix: Remove resource limits from graph downloads (#51)
1 parent 4e08964 commit 9cf6b90

File tree

3 files changed

+56
-16
lines changed

3 files changed

+56
-16
lines changed

motifstudio-web/src/app/GraphStats.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client";
2-
import { useEffect } from "react";
2+
import { useEffect, useState } from "react";
33
import useSWR from "swr";
44
import { HostListing, bodiedFetcher, BASE_URL } from "./api";
55
import { useClientOnly } from "./hooks/useClientOnly";
@@ -25,6 +25,7 @@ export function GraphStats({
2525
onAttributesLoaded?: (attributes: { [key: string]: string }) => void;
2626
}) {
2727
const isClient = useClientOnly();
28+
const [downloadingFormat, setDownloadingFormat] = useState<string | null>(null);
2829

2930
// Fetch graph statistics and attributes.
3031
// TODO: Perhaps these should all go in one combined query?
@@ -80,6 +81,9 @@ export function GraphStats({
8081
* "graphml", "gml", "gexf", "json".
8182
*/
8283
function downloadGraph(format: string = "graphml") {
84+
// Set loading state
85+
setDownloadingFormat(format);
86+
8387
// POST to /api/queries/graph/download with the "host_id" and "format"
8488
// parameters in the body.
8589
fetch(`${BASE_URL}/queries/graph/download`, {
@@ -101,6 +105,14 @@ export function GraphStats({
101105
a.href = url;
102106
a.download = `${graph.name}.${format}`;
103107
a.click();
108+
})
109+
.catch((error) => {
110+
console.error("Download failed:", error);
111+
// You could add error handling UI here if needed
112+
})
113+
.finally(() => {
114+
// Clear loading state
115+
setDownloadingFormat(null);
104116
});
105117
}
106118

@@ -158,14 +170,36 @@ export function GraphStats({
158170
<div className="flex gap-2">
159171
<button
160172
onClick={() => downloadGraph("graphml")}
161-
className="font-bold rounded text-white px-4 bg-blue-500 hover:bg-blue-700"
173+
disabled={downloadingFormat !== null}
174+
className={`font-bold rounded text-white px-4 py-2 flex items-center gap-2 ${
175+
downloadingFormat === "graphml"
176+
? "bg-blue-400 cursor-not-allowed"
177+
: "bg-blue-500 hover:bg-blue-700"
178+
}`}
162179
>
180+
{downloadingFormat === "graphml" && (
181+
<svg className="animate-spin h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
182+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
183+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
184+
</svg>
185+
)}
163186
GraphML
164187
</button>
165188
<button
166189
onClick={() => downloadGraph("gexf")}
167-
className="font-bold rounded text-white px-4 bg-blue-500 hover:bg-blue-700"
190+
disabled={downloadingFormat !== null}
191+
className={`font-bold rounded text-white px-4 py-2 flex items-center gap-2 ${
192+
downloadingFormat === "gexf"
193+
? "bg-blue-400 cursor-not-allowed"
194+
: "bg-blue-500 hover:bg-blue-700"
195+
}`}
168196
>
197+
{downloadingFormat === "gexf" && (
198+
<svg className="animate-spin h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
199+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
200+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
201+
</svg>
202+
)}
169203
GEXF
170204
</button>
171205
</div>

server/src/server/commons.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,10 @@ def run_with_limits(func, args=(), kwargs=None, max_ram_bytes=None, timeout_seco
121121
if proc.is_alive() and hasattr(proc, "kill"):
122122
proc.kill()
123123
proc.join(0)
124+
print(f"Process terminated due to timeout after {timeout_seconds} seconds.")
124125
raise TimeoutError(f"Query exceeded time limit of {timeout_seconds} seconds")
125126
if queue.empty():
127+
print(f"Process terminated unexpectedly, did not return a result, or exceeded {max_ram_bytes} bytes.")
126128
raise MemoryError(f"Query exceeded memory limit of {max_ram_bytes} bytes or terminated unexpectedly")
127129
success, payload = queue.get()
128130
if success:
@@ -166,7 +168,7 @@ def __init__(self, json_filepath_or_dict: str | dict | Path):
166168
ql = config.get("query_limits", {})
167169
self.max_ram_pct = ql.get("max_ram_pct", 0.5)
168170
self.max_ram_bytes = ql.get("max_ram_bytes")
169-
self.max_duration_seconds = ql.get("max_duration_seconds", 120)
171+
self.max_duration_seconds = ql.get("max_duration_seconds", 180)
170172

171173
def get_uri_from_id(self, id: str) -> str | None:
172174
"""Returns the URI of a host from its name.

server/src/server/routers/queries.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,23 @@ def query_graph_download(
8181
)
8282

8383
tic = time.time()
84+
85+
# For resource limits, see below.
86+
nx_graph, error_msg = provider.maybe_get_networkx_graph(uri)
87+
8488
# Enforce query resource limits
85-
ram_limit = commons.max_ram_bytes if commons.max_ram_bytes is not None else int(get_total_ram_bytes() * commons.max_ram_pct)
86-
try:
87-
nx_graph, error_msg = run_with_limits(
88-
provider.maybe_get_networkx_graph,
89-
args=(uri,),
90-
max_ram_bytes=ram_limit,
91-
timeout_seconds=commons.max_duration_seconds,
92-
)
93-
except TimeoutError as e:
94-
raise HTTPException(status_code=504, detail=str(e))
95-
except MemoryError as e:
96-
raise HTTPException(status_code=503, detail=str(e))
89+
# ram_limit = commons.max_ram_bytes if commons.max_ram_bytes is not None else int(get_total_ram_bytes() * commons.max_ram_pct)
90+
# try:
91+
# nx_graph, error_msg = run_with_limits(
92+
# provider.maybe_get_networkx_graph,
93+
# args=(uri,),
94+
# max_ram_bytes=ram_limit,
95+
# timeout_seconds=commons.max_duration_seconds,
96+
# )
97+
# except TimeoutError as e:
98+
# raise HTTPException(status_code=504, detail=str(e))
99+
# except MemoryError as e:
100+
# raise HTTPException(status_code=503, detail=str(e))
97101

98102
def _get_bytes(graph, fmt: _GraphFormats):
99103
# We never prettyprint because we have to send it over the wire next

0 commit comments

Comments
 (0)