Bug Description
HttpStream (packages/apps/src/microsoft_teams/apps/http_stream.py) has a race condition: close() can send the final message while _flush() is still awaiting in-flight _send_activity() calls. This causes the final stream message to arrive before intermediate chunks finish sending.
This is the Python parity of microsoft/teams.ts#549, fixed in microsoft/teams.ts#553. Note: the TS issue's "Bug 1" (streamType override) does not affect Python — close() already calls with_channel_data() before add_stream_final(), which is the correct order.
Root cause
_wait_for_id_and_queue() polls:
while (self._queue or not self._id) and not self._canceled:
await self._state_changed.wait()
self._state_changed.clear()
This checks the queue and _id but not whether _flush() is currently holding self._lock. Inside _flush(), the inner while self._queue loop drains the queue before the awaited _send_activity(...) calls run. While those awaits are pending:
self._queue is empty
self._id is set (after the first chunk completes)
self._lock is still held
close() sees its predicate become false, exits the wait, and proceeds to send the final activity — racing the in-flight chunk send.
Reproduction
stream.emit('chunk 1')
stream.emit('chunk 2')
await stream.close()
# close() may send final message before chunk 2 finishes sending
Proposed fix
Add self._lock.locked() to the predicate in _wait_for_id_and_queue(). The lock primitive is already used elsewhere in close() (line 157 early-return checks self._lock.locked()).
I have a fix and unit test ready to submit as a PR.
Bug Description
HttpStream(packages/apps/src/microsoft_teams/apps/http_stream.py) has a race condition:close()can send the final message while_flush()is still awaiting in-flight_send_activity()calls. This causes the final stream message to arrive before intermediate chunks finish sending.This is the Python parity of microsoft/teams.ts#549, fixed in microsoft/teams.ts#553. Note: the TS issue's "Bug 1" (streamType override) does not affect Python —
close()already callswith_channel_data()beforeadd_stream_final(), which is the correct order.Root cause
_wait_for_id_and_queue()polls:This checks the queue and
_idbut not whether_flush()is currently holdingself._lock. Inside_flush(), the innerwhile self._queueloop drains the queue before the awaited_send_activity(...)calls run. While those awaits are pending:self._queueis emptyself._idis set (after the first chunk completes)self._lockis still heldclose()sees its predicate become false, exits the wait, and proceeds to send the final activity — racing the in-flight chunk send.Reproduction
Proposed fix
Add
self._lock.locked()to the predicate in_wait_for_id_and_queue(). The lock primitive is already used elsewhere inclose()(line 157 early-return checksself._lock.locked()).I have a fix and unit test ready to submit as a PR.