Skip to content

Commit 97bd809

Browse files
committed
pybridge: Asynchronously send package files in chunks
Don't synchronously send the whole document in a single channel data block. They are often quite large (especially in `NODE_ENV=development` mode, but even in production). The synchronous send_data() blocked the bridge for too long, broke flow control, and stalled parallel package channel downloads for too long. That often led to Failed to load resource: net::ERR_INCOMPLETE_CHUNKED_ENCODING browser errors. This got aggravated a lot when going through cockpit-ssh (as it happens on our OSTree images with the cockpit/ws container), but even occasionally happened with the standard setup. Send them in 4K blocks instead, like the C bridge does. Use the same threading approach as our http-stream2 channel to avoid blocking.
1 parent 30b93c6 commit 97bd809

File tree

1 file changed

+17
-5
lines changed

1 file changed

+17
-5
lines changed

src/cockpit/channels/packages.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
# You should have received a copy of the GNU General Public License
1616
# along with this program. If not, see <https://www.gnu.org/licenses/>.
1717

18+
import asyncio
1819
import logging
20+
import threading
1921
from typing import Dict, Optional
2022

2123
from .. import data
@@ -36,6 +38,8 @@ def http_error(self, status: int, message: str) -> None:
3638
template = data.read_cockpit_data_file('fail.html')
3739
self.send_message(status=status, reason='ERROR', headers={'Content-Type': 'text/html; charset=utf-8'})
3840
self.send_data(template.replace(b'@@message@@', message.encode('utf-8')))
41+
self.done()
42+
self.close()
3943

4044
def do_open(self, options: Dict[str, object]) -> None:
4145
self.ready()
@@ -93,9 +97,6 @@ def do_done(self) -> None:
9397

9498
out_headers['Content-Security-Policy'] = policy
9599

96-
self.send_message(status=200, reason='OK', headers=out_headers)
97-
self.send_data(document.data)
98-
99100
except ValueError as exc:
100101
self.http_error(400, str(exc))
101102

@@ -105,5 +106,16 @@ def do_done(self) -> None:
105106
except OSError as exc:
106107
self.http_error(500, f'Internal error: {exc!s}')
107108

108-
self.done()
109-
self.close()
109+
else:
110+
self.send_message(status=200, reason='OK', headers=out_headers)
111+
threading.Thread(args=(asyncio.get_running_loop(), document.data),
112+
target=self.send_document_data,
113+
daemon=True).start()
114+
115+
def send_document_data(self, loop, data):
116+
# split data into 4K blocks, to not overwhelm the channel
117+
block_size = 4096
118+
for i in range(0, len(data), block_size):
119+
loop.call_soon_threadsafe(self.send_data, data[i:i + block_size])
120+
loop.call_soon_threadsafe(self.done)
121+
loop.call_soon_threadsafe(self.close)

0 commit comments

Comments
 (0)