Skip to content

Limit outgoing to_device EDU size to 65536#18416

Merged
MadLittleMods merged 57 commits into
element-hq:developfrom
tchapgouv:edu-limit-size
Mar 24, 2026
Merged

Limit outgoing to_device EDU size to 65536#18416
MadLittleMods merged 57 commits into
element-hq:developfrom
tchapgouv:edu-limit-size

Conversation

@MatMaul

@MatMaul MatMaul commented May 9, 2025

Copy link
Copy Markdown
Contributor

If a set of messages exceeds this limit, the messages are split across several EDUs.

Fix #17035 (should)

There is currently no official specced limit for EDUs, but the consensus seems to be that it would be useful to have one to avoid this bug by bounding the transaction size.

As a side effect it also limits the size of a single to-device message to a bit less than 65536.

This should probably be added to the spec similarly to the message size limit..

Spec PR: matrix-org/matrix-spec#2340

Pull Request Checklist

@MatMaul MatMaul marked this pull request as ready for review May 9, 2025 14:38
@MatMaul MatMaul requested a review from a team as a code owner May 9, 2025 14:38
Comment thread synapse/api/constants.py
Comment thread synapse/handlers/devicemessage.py Outdated
Comment thread synapse/handlers/devicemessage.py Outdated
Comment thread synapse/handlers/devicemessage.py Outdated
Comment thread synapse/handlers/devicemessage.py Outdated
Comment thread synapse/handlers/devicemessage.py Outdated
Comment thread synapse/handlers/devicemessage.py
Comment thread synapse/handlers/devicemessage.py Outdated
Comment thread tests/rest/client/test_sendtodevice.py Outdated
Comment thread tests/rest/client/test_sendtodevice.py Outdated
Comment thread synapse/handlers/devicemessage.py Outdated
@MadLittleMods MadLittleMods changed the title Limit to_device EDU size to 65536 Limit outgoing to_device EDU size to 65536 Jun 3, 2025
MadLittleMods added a commit that referenced this pull request Jun 19, 2025
@mcalinghee

Copy link
Copy Markdown
Contributor

Looks like the CI tests are flaky as mentioned here : #18537

Comment thread tests/rest/client/test_sendtodevice.py Outdated
@mcalinghee

Copy link
Copy Markdown
Contributor

Not able to reproduce the issue here by running COMPLEMENT_DIR=../complement POSTGRES=1 WORKERS=1 ./scripts-dev/complement.sh
Probably missing something here.

@MatMaul MatMaul requested a review from MadLittleMods March 23, 2026 08:57
@MadLittleMods

MadLittleMods commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Relevant thread is here: #18416 (comment) (press 'load more' when viewing the conversations to get everything visible)

We should link your spec PR in the code where we raise EventSizeError

@MadLittleMods MadLittleMods removed their request for review March 23, 2026 20:23
@MatMaul

MatMaul commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author
image Thank you stupid GH for this great view of a comment with no context and no way to easily reach the context :)

Thanks for the direct link, MadLittleMods >> GitHub.

@MatMaul MatMaul requested a review from MadLittleMods March 23, 2026 21:36
Comment thread synapse/handlers/devicemessage.py Outdated

@MadLittleMods MadLittleMods left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I'm unable to apply the last edits

Co-authored-by: Eric Eastwood <madlittlemods@gmail.com>
@MatMaul MatMaul requested a review from MadLittleMods March 24, 2026 09:00
@MadLittleMods MadLittleMods merged commit 7fad50f into element-hq:develop Mar 24, 2026
74 of 76 checks passed
@MadLittleMods

Copy link
Copy Markdown
Contributor

Thanks for the fix @MatMaul 🐏

@MatMaul

MatMaul commented Mar 24, 2026

Copy link
Copy Markdown
Contributor Author

We did it! Thanks for the reviews 🙏

MadLittleMods added a commit that referenced this pull request Mar 24, 2026
erikjohnston added a commit that referenced this pull request Mar 27, 2026
erikjohnston added a commit that referenced this pull request Mar 27, 2026
Reverts #18416


Unfortunately, this causes failures on `/sendToDevice` endpoint in
normal circumstances. If a single user has, say, a hundred devices then
we easily go over the limit. This blocks message sending entirely in
encrypted rooms.

cc @MadLittleMods @MatMaul
erikjohnston pushed a commit that referenced this pull request Mar 27, 2026
If a set of messages exceeds this limit, the messages are split
across several EDUs.

Fix #17035 (should)

There is currently [no official specced limit for
EDUs](matrix-org/matrix-spec#807), but the
consensus seems to be that it would be useful to have one to avoid this
bug by bounding the transaction size.

As a side effect it also limits the size of a single to-device message
to a bit less than 65536.

This should probably be added to the spec similarly to the [message size
limit.](https://spec.matrix.org/v1.14/client-server-api/#size-limits)

Spec PR: matrix-org/matrix-spec#2340

---------

Co-authored-by: mcalinghee <mcalinghee.dev@gmail.com>
Co-authored-by: Eric Eastwood <madlittlemods@gmail.com>
erikjohnston added a commit that referenced this pull request Mar 27, 2026
This is based on #18416, which got reverted due to it rejecting
to-device messages to users with many devices.

The main change here is that if a to-device EDU for a single user is too
large, then we split it up into multiple EDUs.
erikjohnston added a commit that referenced this pull request Mar 27, 2026
This is based on #18416, which got reverted due to it rejecting
to-device messages to users with many devices.

The main change here is that if a to-device EDU for a single user is too
large, then we split it up into multiple EDUs.
erikjohnston added a commit that referenced this pull request Jun 2, 2026
This is based on #18416, which
got reverted (#19614) due to it incorrectly rejecting to-device messages
to users with many devices (and thus breaking message sending).

Fix #17035

A to-device message content looks like:

```jsonc
{
  "@user:domain": {"device1": {...}, "device2": {...}},
  ...
}
```

The previous PR would split up into multiple EDUs, each with a subset of
the users. However, if one user's entry was too large it would not
further split it up and then error out.

The main change in this PR is to allow splitting up a single user into
multiple EDUs.

Other changes:
1. Rename to `SOFT_MAX_EDU_SIZE` to indicate that we sometimes send EDUs
with larger size than that, and its more a target than a hard limit.
2. Check early if any to-device message (to a specific device) is too
large to send, even if we're not going to send it over federation. This
ensures that we catch issues where clients try to send too large
to-device.

This still means that if a client send a large individual to-device
message it will fail, but I don't believe we ever send such large
to-device messages (normally they're in the range of a few KB).

---

I ended up changing the implementation a bunch to make it easy to reuse
the code to split up dictionaries. Instead of repeatedly splitting up
the EDU until each bit fits into the size, we instead record the size of
each entry in the dict and instead split up based on cumulative size.
This means we call `encode_canonical_json` on each entry rather than
once on the entire struct, but its not significantly slower to do so.

--

cc @MatMaul @MadLittleMods

---------

Co-authored-by: Mathieu Velten <matmaul@gmail.com>
Co-authored-by: mcalinghee <mcalinghee.dev@gmail.com>
Co-authored-by: Eric Eastwood <madlittlemods@gmail.com>
FrenchGithubUser pushed a commit to famedly/synapse-upstreaming that referenced this pull request Jun 12, 2026
If a set of messages exceeds this limit, the messages are split
across several EDUs.

Fix element-hq#17035 (should)

There is currently [no official specced limit for
EDUs](matrix-org/matrix-spec#807), but the
consensus seems to be that it would be useful to have one to avoid this
bug by bounding the transaction size.

As a side effect it also limits the size of a single to-device message
to a bit less than 65536.

This should probably be added to the spec similarly to the [message size
limit.](https://spec.matrix.org/v1.14/client-server-api/#size-limits)

Spec PR: matrix-org/matrix-spec#2340

---------

Co-authored-by: mcalinghee <mcalinghee.dev@gmail.com>
Co-authored-by: Eric Eastwood <madlittlemods@gmail.com>
FrenchGithubUser pushed a commit to famedly/synapse-upstreaming that referenced this pull request Jun 12, 2026
FrenchGithubUser pushed a commit to famedly/synapse-upstreaming that referenced this pull request Jun 12, 2026
Reverts element-hq#18416


Unfortunately, this causes failures on `/sendToDevice` endpoint in
normal circumstances. If a single user has, say, a hundred devices then
we easily go over the limit. This blocks message sending entirely in
encrypted rooms.

cc @MadLittleMods @MatMaul
FrenchGithubUser pushed a commit to famedly/synapse-upstreaming that referenced this pull request Jun 12, 2026
This is based on element-hq#18416, which
got reverted (element-hq#19614) due to it incorrectly rejecting to-device messages
to users with many devices (and thus breaking message sending).

Fix element-hq#17035

A to-device message content looks like:

```jsonc
{
  "@user:domain": {"device1": {...}, "device2": {...}},
  ...
}
```

The previous PR would split up into multiple EDUs, each with a subset of
the users. However, if one user's entry was too large it would not
further split it up and then error out.

The main change in this PR is to allow splitting up a single user into
multiple EDUs.

Other changes:
1. Rename to `SOFT_MAX_EDU_SIZE` to indicate that we sometimes send EDUs
with larger size than that, and its more a target than a hard limit.
2. Check early if any to-device message (to a specific device) is too
large to send, even if we're not going to send it over federation. This
ensures that we catch issues where clients try to send too large
to-device.

This still means that if a client send a large individual to-device
message it will fail, but I don't believe we ever send such large
to-device messages (normally they're in the range of a few KB).

---

I ended up changing the implementation a bunch to make it easy to reuse
the code to split up dictionaries. Instead of repeatedly splitting up
the EDU until each bit fits into the size, we instead record the size of
each entry in the dict and instead split up based on cumulative size.
This means we call `encode_canonical_json` on each entry rather than
once on the entire struct, but its not significantly slower to do so.

--

cc @MatMaul @MadLittleMods

---------

Co-authored-by: Mathieu Velten <matmaul@gmail.com>
Co-authored-by: mcalinghee <mcalinghee.dev@gmail.com>
Co-authored-by: Eric Eastwood <madlittlemods@gmail.com>
FrenchGithubUser pushed a commit to famedly/synapse that referenced this pull request Jun 30, 2026
This is based on element-hq/synapse#18416, which
got reverted (#19614) due to it incorrectly rejecting to-device messages
to users with many devices (and thus breaking message sending).

Fix element-hq/synapse#17035

A to-device message content looks like:

```jsonc
{
  "@user:domain": {"device1": {...}, "device2": {...}},
  ...
}
```

The previous PR would split up into multiple EDUs, each with a subset of
the users. However, if one user's entry was too large it would not
further split it up and then error out.

The main change in this PR is to allow splitting up a single user into
multiple EDUs.

Other changes:
1. Rename to `SOFT_MAX_EDU_SIZE` to indicate that we sometimes send EDUs
with larger size than that, and its more a target than a hard limit.
2. Check early if any to-device message (to a specific device) is too
large to send, even if we're not going to send it over federation. This
ensures that we catch issues where clients try to send too large
to-device.

This still means that if a client send a large individual to-device
message it will fail, but I don't believe we ever send such large
to-device messages (normally they're in the range of a few KB).

---

I ended up changing the implementation a bunch to make it easy to reuse
the code to split up dictionaries. Instead of repeatedly splitting up
the EDU until each bit fits into the size, we instead record the size of
each entry in the dict and instead split up based on cumulative size.
This means we call `encode_canonical_json` on each entry rather than
once on the entire struct, but its not significantly slower to do so.

--

cc @MatMaul @MadLittleMods

---------

Co-authored-by: Mathieu Velten <matmaul@gmail.com>
Co-authored-by: mcalinghee <mcalinghee.dev@gmail.com>
Co-authored-by: Eric Eastwood <madlittlemods@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A long queue of to-device messages can prevent outgoing federation working

5 participants