Skip to content

Conversation

@colin-nl
Copy link
Contributor

@colin-nl colin-nl commented Jan 30, 2026

Related to Issue #27804

Problem: When two WebSocket messages arrive in quick succession, the second message is lost entirely.

Root Cause: In handleOrder and handlePositions, the code checks findMessageHashes to see if there's an active subscriber before adding data to the cache. When client.resolve() is called, it deletes the future from client.futures. If a second message arrives before the caller can re-invoke watchOrders, findMessageHashes returns empty and the entire handler body is skipped. The order/position is never added to the cache.

The Fix: Always add incoming data to the cache first, then check for subscribers only when deciding whether to call client.resolve().

Questions/Discussion: I noticed that handleBalance and handleMyTrade don't call findMessageHashes like handleOrder and handlePositions do. There may be some context that I'm missing that could make this PR more correct.

Problem: When two WebSocket messages arrive in quick succession, the second message is lost entirely.

Root Cause: In handleOrder and handlePositions, the code checks findMessageHashes() to see if there's an active subscriber before adding data to the cache. When client.resolve() is called, it deletes the future from client.futures. If a second message arrives before the caller can re-invoke watchOrders(), findMessageHashes() returns empty and the entire handler body is skipped—the order/position is never added to the cache.

The Fix: Always add incoming data to the cache first, then check for subscribers only when deciding whether to call client.resolve().
@kroitor
Copy link
Member

kroitor commented Jan 31, 2026

Thx for the PR @colin-nl ! We will run some tests and will merge it asap.

Questions/Discussion: I noticed that handleBalance and handleMyTrade don't call findMessageHashes like handleOrder and handlePositions do. There may be some context that I'm missing that could make this PR more correct.

On handleBalance – i think this is due to the nature of the stream (it's the same messageHash for all watchers), as for the handleMyTrade – we will take a deeper look into it and will revert to you on this.

@carlosmiei
Copy link
Collaborator

@colin-nl Running this code a few times we can see it ocasionally (using master version), will check using your PR

async def watch_orders():
    while True:
        orders = await exchange.watch_orders(params = {'type':'swap'})
        for order in orders:
            print('WatchOrders:', order['id'], order['status'], order['symbol'], order['filled'], order['remaining'])

async def place_order(symbol, type, side, amount, price = None):
    order = await exchange.create_order(symbol, type, side, amount, price)
    print("Placed order:", order['id'], order['status'], order['symbol'], order['filled'], order['remaining'])
    return order

async def cancel_order(order_id, symbol):
    result = await exchange.cancel_order(order_id, symbol)
    print("Cancelled order:", result['id'], result['status'])
    return result

async def example_1():
    markets = await exchange.load_markets()

    watch_task = asyncio.create_task(watch_orders())
    await asyncio.sleep(5)

    order1 = place_order('ADA/USDT:USDT', 'limit', 'buy', 20, 0.3)
    order2 = place_order('ASTER/USDT:USDT', 'limit', 'buy', 20, 0.544)

    res = await asyncio.gather(order1, order2)
    await asyncio.sleep(5)

    cancel1 = cancel_order(res[0]['id'], res[0]['symbol'])
    cancel2 = cancel_order(res[1]['id'], res[1]['symbol'])

    await asyncio.gather(cancel1, cancel2)
    await asyncio.sleep(10)
    await watch_task

@carlosmiei
Copy link
Collaborator

@colin-nl And yeah findMessageHashes is a legacy method that should be rarely used and never at that point, thanks for the fix.

@carlosmiei
Copy link
Collaborator

Log with the fix

CCXT Version: 4.5.35
Placed order: 1768034040 open ADA/USDT:USDT 0.0 20.0
WatchOrders: 1768034040 open ADA/USDT:USDT 0.0 20.0
Placed order: 2385377294 open ASTER/USDT:USDT 0.0 20.0
WatchOrders: 2385377294 open ASTER/USDT:USDT 0.0 20.0
Cancelled order: 1768034040 canceled
Cancelled order: 2385377294 canceled
WatchOrders: 1768034040 canceled ADA/USDT:USDT 0.0 20.0
WatchOrders: 2385377294 canceled ASTER/USDT:USDT 0.0 20.0


@carlosmiei carlosmiei merged commit d90add2 into ccxt:master Jan 31, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants