@@ -289,3 +289,54 @@ async def test_exchangews_get_ohlcv(mocker, caplog):
289289 assert log_has_re (msg , caplog )
290290
291291 exchange_ws .cleanup ()
292+
293+
294+ def test_exchangews_continuous_stopped_task_exception (mocker , caplog ):
295+ config = MagicMock ()
296+ ccxt_object = MagicMock ()
297+ ccxt_object .ohlcvs = {
298+ "ETH/USDT" : {
299+ "1m" : [
300+ [1635840000000 , 100 , 200 , 300 , 400 , 500 ],
301+ [1635840060000 , 101 , 201 , 301 , 401 , 501 ],
302+ [1635840120000 , 102 , 202 , 302 , 402 , 502 ],
303+ ]
304+ }
305+ }
306+ mocker .patch ("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever" , MagicMock ())
307+
308+ exchange_ws = ExchangeWS (config , ccxt_object )
309+ exchange_ws ._loop = MagicMock ()
310+ exchange_ws ._loop .is_closed .return_value = False
311+
312+ paircomb = ("ETH/USDT" , "1m" , CandleType .SPOT )
313+ exchange_ws ._klines_scheduled .add (paircomb )
314+ exchange_ws .klines_last_refresh [paircomb ] = 1
315+
316+ task = MagicMock ()
317+ task .cancelled .return_value = False
318+ task .result .side_effect = RuntimeError ("unexpected" )
319+ exchange_ws ._background_tasks .add (task )
320+
321+ completed_future = MagicMock ()
322+ completed_future .result .return_value = None
323+
324+ def side_effect (coro , loop ):
325+ coro .close ()
326+ return completed_future
327+
328+ run_threadsafe = mocker .patch (
329+ "freqtrade.exchange.exchange_ws.asyncio.run_coroutine_threadsafe" ,
330+ side_effect = side_effect ,
331+ )
332+
333+ exchange_ws ._continuous_stopped (task , "ETH/USDT" , "1m" , CandleType .SPOT )
334+
335+ assert task not in exchange_ws ._background_tasks
336+ assert paircomb not in exchange_ws ._klines_scheduled
337+ assert paircomb not in exchange_ws .klines_last_refresh
338+ assert ccxt_object .ohlcvs ["ETH/USDT" ].get ("1m" ) is None
339+ assert run_threadsafe .call_count == 1
340+ assert log_has_re ("Unhandled exception in watch task callback for ETH/USDT, 1m" , caplog )
341+
342+ exchange_ws .cleanup ()
0 commit comments