| 191 | |
| 192 | |
| 193 | class ZLibCompressor: |
| 194 | def __init__( |
| 195 | self, |
| 196 | encoding: str | None = None, |
| 197 | suppress_deflate_header: bool = False, |
| 198 | level: int | None = None, |
| 199 | wbits: int | None = None, |
| 200 | strategy: int | None = None, |
| 201 | executor: Executor | None = None, |
| 202 | max_sync_chunk_size: int | None = MAX_SYNC_CHUNK_SIZE, |
| 203 | ): |
| 204 | self._executor = executor |
| 205 | self._max_sync_chunk_size = max_sync_chunk_size |
| 206 | self._mode = ( |
| 207 | encoding_to_mode(encoding, suppress_deflate_header) |
| 208 | if wbits is None |
| 209 | else wbits |
| 210 | ) |
| 211 | self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend) |
| 212 | |
| 213 | kwargs: CompressObjArgs = {} |
| 214 | kwargs["wbits"] = self._mode |
| 215 | if strategy is not None: |
| 216 | kwargs["strategy"] = strategy |
| 217 | if level is not None: |
| 218 | kwargs["level"] = level |
| 219 | self._compressor = self._zlib_backend.compressobj(**kwargs) |
| 220 | |
| 221 | def compress_sync(self, data: Buffer) -> bytes: |
| 222 | return self._compressor.compress(data) |
| 223 | |
| 224 | async def compress(self, data: Buffer) -> bytes: |
| 225 | """Compress the data and returned the compressed bytes. |
| 226 | |
| 227 | Note that flush() must be called after the last call to compress() |
| 228 | |
| 229 | If the data size is large than the max_sync_chunk_size, the compression |
| 230 | will be done in the executor. Otherwise, the compression will be done |
| 231 | in the event loop. |
| 232 | |
| 233 | **WARNING: This method is NOT cancellation-safe when used with flush().** |
| 234 | If this operation is cancelled, the compressor state may be corrupted. |
| 235 | The connection MUST be closed after cancellation to avoid data corruption |
| 236 | in subsequent compress operations. |
| 237 | |
| 238 | For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap |
| 239 | compress() + flush() + send operations in a shield and lock to ensure atomicity. |
| 240 | """ |
| 241 | # For large payloads, offload compression to executor to avoid blocking event loop |
| 242 | should_use_executor = ( |
| 243 | self._max_sync_chunk_size is not None |
| 244 | and len(data) > self._max_sync_chunk_size |
| 245 | ) |
| 246 | if should_use_executor: |
| 247 | return await asyncio.get_running_loop().run_in_executor( |
| 248 | self._executor, self._compressor.compress, data |
| 249 | ) |
| 250 | return self.compress_sync(data) |
no outgoing calls