@@ -116,7 +116,8 @@ async def download(self, delivery_key: str) -> AsyncGenerator[bytes, None]:
116116 chunk = None
117117
118118 if not chunk :
119- # Empty chunk - the app process has finished sending the file.
119+ # Empty chunk - the app process has finished sending the file
120+ # or the download has been cancelled.
120121 incoming_chunks .task_done ()
121122 del self ._active_downloads [delivery_key ]
122123 break
@@ -131,7 +132,14 @@ async def chunk_received(self, delivery_key: str, chunk: bytes | str) -> None:
131132 delivery_key: The delivery key that the chunk was received for.
132133 chunk: The chunk that was received.
133134 """
134- download = self ._active_downloads [delivery_key ]
135+
136+ download = self ._active_downloads .get (delivery_key )
137+ if not download :
138+ # The download may have been cancelled - e.g. the websocket
139+ # was closed before the download could complete.
140+ log .debug ("Chunk received for cancelled download %r" , delivery_key )
141+ return
142+
135143 if isinstance (chunk , str ):
136144 chunk = chunk .encode (download .encoding or "utf-8" )
137145 await download .incoming_chunks .put (chunk )
@@ -155,3 +163,13 @@ async def get_download_metadata(self, delivery_key: str) -> Download:
155163 delivery_key: The delivery key to get the metadata for.
156164 """
157165 return self ._active_downloads [delivery_key ]
166+
167+ async def cancel_app_downloads (self , app_service_id : str ) -> None :
168+ """Cancel all downloads for the given app service.
169+
170+ Args:
171+ app_service_id: The app service ID to cancel downloads for.
172+ """
173+ for download in self ._active_downloads .values ():
174+ if download .app_service .app_service_id == app_service_id :
175+ await download .incoming_chunks .put (None )
0 commit comments