Enhancement: Check ComfyUI server cache before image upload#2468
Enhancement: Check ComfyUI server cache before image upload#2468rbeurskens wants to merge 2 commits into
Conversation
… if the server closes the connection on a cache hit before the client sends the PUT payload.
|
I've never encountered the "Unable to write" error before, does that happen all the time for you or with particular image size? Which OS? I don't think an additional HEAD request is a necessarily good trade of, now every upload needs an extra round trip, and the not-cached case is probably far more common. I consider sending some of the upload data even if it's cached okay, but it would require a way to selectively ignore this particular error, which might be difficult if Qt doesn't return the actual response and 200 status code. Since I can't reproduce it, it's hard to tell. Cleaner solution might be to start the PUT with a |
|
For me, consistent from consecutive prompts using one or more of the same source image(s) when using a custom ComfyUI server (latest) running on Kubuntu LTS 24.04 on a 2012 era PC (MSI Z77 GD65 mainboard, i7 3770K, 16GB). Krita running on Windows 10 12gen i7,16GB. Images were not really large (this one was ~1MB ) |
|
Might just have to do with latency, I mostly test local with Krita & Comfy running on the same machine. Note that the |
|
Yes, probably. I never had this when running the local managed server. After all, the loopback likely doesn't have this issue. But with an actual remote it's different. (maybe even worse with a cloud server instead of one in the LAN) The problem is that, as you also said, |
|
Not sure why you think it needs to be handled at a lower level, the As far as I understand you need to change the client to send I'm not 100% sure it can be done with |
|
Because |
|
A bit more context of what I tried before: I added an error handler in def debug_log(err):
status = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
log.info(f"DEBUG: Error {err} occurred. Status: {status}")
reply.errorOccurred.connect(debug_log)This resulted in the following: (note status is |
This comment was marked as outdated.
This comment was marked as outdated.
|
Yes it absolutely needs server side support! My hope was it can just be added here: https://github.com/Acly/comfyui-tooling-nodes/blob/main/api.py#L351-L366 This code is maintained by me so we can change it without modifying ComfyUI. |
|
Ah, cool. That is good to know. We could modify it something like this: (untested draft) @_server.routes.put("/api/etn/image/{id}")
async def put_image(request: web.Request):
try:
id = request.match_info.get("id", "")
if id in image_cache:
# If client sent Expect: 100-continue, this 200 OK
# tells it: "Don't send the data."
# request.release() ensures any stray bytes are handled.
await request.release()
return web.json_response(dict(status="cached"), status=200)
# Accessing request.content now triggers the '100 Continue'
# signal to the client to start uploading.
content_type = request.headers.get("Content-Type", "application/octet-stream")
data = bytearray()
async for chunk, _ in request.content.iter_chunks():
data.extend(chunk)
image_cache.insert(id, bytes(data), content_type)
return web.json_response(dict(status="success"), status=201)
except Exception as e:
# If upload fails mid-way, request.release() helps clean up
await request.release()
return web.json_response(dict(error=str(e)), status=500).. and modify the client code to make the decision based on the def http(
self,
method: str,
url: str,
data: dict | QByteArray | None = None,
timeout: float | None = None,
bearer: str | None = None,
):
self._cleanup()
request = self._prepare_request(url, timeout, bearer)
assert method in ["GET", "POST", "PUT", "HEAD"]
if method == "POST":
# (...)
elif method == "PUT":
if isinstance(data, bytes):
data = QByteArray(data)
assert isinstance(data, QByteArray)
# --- Handshake Optimization ---
# Tell the server we have a payload but want to check headers first.
# This prevents Error 99/Socket Error 10 by allowing the server
# to return "cached" before the client starts the upload.
request.setRawHeader(b"Expect", b"100-continue")
request.setHeader(
QNetworkRequest.KnownHeaders.ContentTypeHeader, "application/octet-stream"
)
request.setHeader(QNetworkRequest.KnownHeaders.ContentLengthHeader, data.size())
reply = self._net.put(request, data)
else:
reply = self._net.get(request)
future = asyncio.get_running_loop().create_future()
self._requests[reply] = Request(url, future)
return future |
|
This is what I mean: Acly/comfyui-tooling-nodes#65 I don't have time to fully test right now, maybe you can check if it works. I can review/merge later. |
|
Sure I can test it as I have the environment to reproduce the problem. I will let you know. |
|
I merged the server-side changes. This should already help, but I'm not sure it uses the efficient path. Did you ever check if the client sends the |
I tested it with repeated uploads with curl. And this already looks like how it should look : PS>> curl.exe -v -X PUT --data-binary "@my_image.jpg" http://<myserver>:8188/api/etn/image/1d59b868
* Trying <myserver>:8188...
* Connected to <myserver> (<server_ip>) port 8188
* using HTTP/1.x
> PUT /api/etn/image/1d59b868 HTTP/1.1
> Host: <myserver>:8188
> User-Agent: curl/8.13.0
> Accept: */*
> Content-Length: 1067265
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
>
* Done waiting for 100-continue
* upload completely sent off: 1067265 bytes
< HTTP/1.1 201 Created
< Content-Type: application/json; charset=utf-8
< Content-Length: 21
< Date: Thu, 14 May 2026 01:09:47 GMT
< Server: Python/3.12 aiohttp/3.13.5
<
{"status": "success"}* Connection #0 to host <myserver> left intactPS>> curl.exe -v -X PUT --data-binary "@my_image.jpg" http://<myserver>:8188/api/etn/image/1d59b868
* Trying <myserver>:8188...
* Connected to <myserver> (<server_ip>) port 8188
* using HTTP/1.x
> PUT /api/etn/image/1d59b868 HTTP/1.1
> Host: <myserver>:8188
> User-Agent: curl/8.13.0
> Accept: */*
> Content-Length: 1067265
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Content-Length: 20
< Date: Thu, 14 May 2026 01:10:42 GMT
< Server: Python/3.12 aiohttp/3.13.5
<
{"status": "cached"}* Connection #0 to host <myserver> left intactSo server side should be good. I'll modify the client code in this PR to handle it like |
|
I have tested it with the small adjustment I thought that would work (just adding the header), but it seems WIP... |
|
Nevermind then, if it's not possible with Qt's HTTP features I don't think we want it. Reimplementing this with sockets is not worth the complexity. Qt6 migration is imminent, not sure if it will help but worth checking out. |
|
I'm implementing it for myself anyway, and it will be easy to toggle it only for this workflow. I'll share it when finished. I'm already testing it. (it's not even certain |
Should solve #2467
Also saves some network traffic by not sending the payload if HEAD returned 200. (in contrast to PUT)