OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge commits / 7409fc7

Fix reconnect loop: handle send buffer backpressure
Root cause: baking textures generates hundreds/thousands of tile_ready messages. sendall() on the non-blocking UDS socket raises BlockingIOError when the kernel send buffer fills up (~200KB). The except OSError handler treated this as a fatal disconnect, triggering reconnect. Reconnect clears _texture_ids, causing re-request → re-bake → re-flood → infinite loop. Fix: Replace sendall() with manual send()+select() loop. When the send buffer is full (BlockingIOError), wait up to 5s for writability via select() instead of disconnecting. Also fix Godot-side _write_all() to use poll() instead of spinning on EAGAIN. v0.17.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7409fc769739099691270d3e65374551451b98ef
by plankatron <fleeky@gmail.com> · 2026-03-04T19:46:15.000Z

Changed files (4) +36 -9

blender_addon/blender_manifest.toml +1 -1
……@@ -2,7 +2,7 @@ schema_version = "1.0.0"
22id = "blender_godot_bridge"
33name = "Blender-Godot Bridge"
44tagline = "Real-time bidirectional scene sync with Godot"
5version = "0.17.0"
5version = "0.17.1"
66blender_version_min = "4.2.0"
77type = "add-on"
88maintainer = "blender-bridge <noreply@localhost>"
blender_addon/connection.py +26 -5
……@@ -4,6 +4,7 @@ Length-prefixed messages: [4-byte LE length] [payload].
44Non-blocking receive for use with Blender's timer callbacks.
55"""
66
7import select
78import socket
89import struct
910
……@@ -71,13 +72,33 @@ class BridgeConnection:
7172 # ── Send ─────────────────────────────────────────────────────────────
7273
7374 def send_message(self, data: bytes):
74 """Send a length-prefixed message."""
75 """Send a length-prefixed message.
76
77 Handles BlockingIOError (send buffer full) by waiting for writability
78 instead of treating it as a fatal disconnect. This is critical when
79 streaming many tile_ready messages — the non-blocking socket's send
80 buffer can fill up, and sendall() would raise BlockingIOError.
81 """
7582 if self._sock is None:
7683 return
77 try:
78 self._sock.sendall(struct.pack("<I", len(data)) + data)
79 except OSError:
80 self._close_unexpected()
84 frame = struct.pack("<I", len(data)) + data
85 sent = 0
86 while sent < len(frame):
87 try:
88 n = self._sock.send(frame[sent:])
89 if n == 0:
90 self._close_unexpected()
91 return
92 sent += n
93 except BlockingIOError:
94 # Send buffer full — wait up to 5s for writability
95 _, writable, _ = select.select([], [self._sock], [], 5.0)
96 if not writable:
97 self._close_unexpected()
98 return
99 except OSError:
100 self._close_unexpected()
101 return
81102
82103 # ── Receive (non-blocking) ───────────────────────────────────────────
83104
src/blender_bridge.cpp +1 -1
……@@ -264,7 +264,7 @@ Error BlenderBridge::start_server(const String &socket_path) {
264264 _known_instance_ids.clear();
265265 _next_object_id = 1;
266266
267 UtilityFunctions::print("BlenderBridge v0.17.0: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")");
267 UtilityFunctions::print("BlenderBridge v0.17.1: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")");
268268 return OK;
269269}
270270
src/uds_server.h +8 -2
……@@ -234,8 +234,14 @@ private:
234234 } else if (n < 0) {
235235 if (errno == EINTR)
236236 continue;
237 if (errno == EAGAIN || errno == EWOULDBLOCK)
238 continue; // Spin briefly for non-blocking (messages are small)
237 if (errno == EAGAIN || errno == EWOULDBLOCK) {
238 // Wait for socket to become writable (up to 2s)
239 struct pollfd pfd = { fd, POLLOUT, 0 };
240 int r = ::poll(&pfd, 1, 2000);
241 if (r <= 0)
242 return false; // Timeout or error
243 continue;
244 }
239245 return false; // Real error
240246 }
241247 }