@@ -86,16 +86,9 @@ func (r *RedditClient) runSync(ctx context.Context, done chan struct{}) {
8686 continue
8787 }
8888
89- err = r .handleSync (ctx , resp )
90- if err != nil {
91- log .Error ().Err (err ).Msg ("Failed to process Reddit sync; retaining cursor" )
92- r .userLogin .BridgeState .Send (status.BridgeState {StateEvent : status .StateTransientDisconnect , Error : "reddit-sync-apply-failed" , Message : "Unable to synchronize Reddit chat state. Retrying." })
93- select {
94- case <- time .After (backoff ):
95- case <- ctx .Done ():
96- return
97- }
98- continue
89+ r .handleSync (ctx , resp )
90+ if ctx .Err () != nil {
91+ return
9992 }
10093 // All framework event calls have returned. Advance the native token;
10194 // event delivery and crash safety belong to bridgev2 and its runtime.
@@ -118,90 +111,79 @@ func (r *RedditClient) runSync(ctx context.Context, done chan struct{}) {
118111 }
119112}
120113
121- func (r * RedditClient ) handleSync (ctx context.Context , resp * redditchat.SyncResponse ) error {
122- unhidden , err := r .applyHiddenChatData (ctx , resp )
123- if err != nil {
124- return err
125- }
114+ // Failures are isolated per room and event: a deterministic failure must not
115+ // retain the cursor and stop every other chat on the account.
116+ func (r * RedditClient ) handleSync (ctx context.Context , resp * redditchat.SyncResponse ) {
117+ unhidden := r .applyHiddenChatData (ctx , resp )
126118 for roomID , joined := range resp .Rooms .Join {
127119 if r .meta .HiddenRooms [roomID ] {
128120 continue
129121 }
130- if err := r .handleRoomUpdate (ctx , roomID , joined ); err != nil {
131- return err
132- }
122+ logRoomError (ctx , roomID , r .handleRoomUpdate (ctx , roomID , joined ))
133123 }
134124 for roomID , invited := range resp .Rooms .Invite {
135125 if r .meta .HiddenRooms [roomID ] {
136126 continue
137127 }
138- if err := r .handleInvitedRoom (ctx , roomID , invited ); err != nil {
139- return err
140- }
128+ logRoomError (ctx , roomID , r .handleInvitedRoom (ctx , roomID , invited ))
141129 }
142130 // Pending requests receive subsequent messages in rooms.peek. Process
143131 // invitation state first, then use the same queue/backfill path as joined
144132 // timelines. Membership comes from state, never from the response bucket.
145133 for roomID , peek := range resp .Rooms .Peek {
146- if r .meta .HiddenRooms [roomID ] || resp .Rooms .Join [roomID ] != nil || resp .Rooms .Leave [roomID ] != nil {
134+ if peek == nil || r .meta .HiddenRooms [roomID ] || resp .Rooms .Join [roomID ] != nil || resp .Rooms .Leave [roomID ] != nil {
147135 continue
148136 }
149137 if len (peek .State .Events )+ len (peek .Timeline .Events )+ len (peek .Ephemeral .Events ) == 0 {
150138 continue
151139 }
152- if err := r .handleRoomUpdate (ctx , roomID , peek ); err != nil {
153- return err
154- }
140+ logRoomError (ctx , roomID , r .handleRoomUpdate (ctx , roomID , peek ))
155141 }
156142 for roomID := range resp .Rooms .Leave {
157- if err := r .handleLeftRoom (ctx , roomID ); err != nil {
158- return err
159- }
143+ logRoomError (ctx , roomID , r .handleLeftRoom (ctx , roomID ))
160144 }
161145 if len (unhidden ) > 0 {
162146 state , err := r .fetchRoomSnapshots (ctx , unhidden )
163147 if err != nil {
164- return err
148+ if ctx .Err () == nil {
149+ zerolog .Ctx (ctx ).Err (err ).Msg ("Failed to fetch unhidden Reddit chats, skipping" )
150+ }
151+ return
165152 }
166- return r .handleSync (ctx , state )
153+ r .handleSync (ctx , state )
154+ }
155+ }
156+
157+ func logRoomError (ctx context.Context , roomID id.RoomID , err error ) {
158+ if err != nil && ctx .Err () == nil {
159+ zerolog .Ctx (ctx ).Err (err ).Stringer ("room_id" , roomID ).Msg ("Failed to handle Reddit room update, skipping" )
167160 }
168- return nil
169161}
170162
171- func (r * RedditClient ) applyHiddenChatData (ctx context.Context , resp * redditchat.SyncResponse ) ( []id.RoomID , error ) {
163+ func (r * RedditClient ) applyHiddenChatData (ctx context.Context , resp * redditchat.SyncResponse ) []id.RoomID {
172164 var unhidden []id.RoomID
173165 // Apply account data before invitations: full native sync includes hidden
174166 // DMs in both peek and invite. Never mistake a preview for membership.
175167 for _ , rooms := range []map [id.RoomID ]* mautrix.SyncJoinedRoom {resp .Rooms .Join , resp .Rooms .Peek } {
176168 for roomID , room := range rooms {
177169 if room == nil {
178- return nil , errors . New ( "reddit sync contains a null room" )
170+ continue
179171 }
180172 for _ , evt := range room .AccountData .Events {
181173 if evt == nil || evt .Type .Type != "com.reddit.hidden_chat" {
182174 continue
183175 }
184- var content struct {
185- Hidden * bool `json:"hidden"`
186- }
187- raw , err := json .Marshal (& evt .Content )
176+ hidden , err := parseHiddenChat (evt )
188177 if err != nil {
189- return nil , err
190- }
191- if err = json .Unmarshal (raw , & content ); err != nil {
192- return nil , err
193- }
194- if content .Hidden == nil {
195- return nil , errors .New ("reddit hidden chat state has no hidden flag" )
178+ logRoomError (ctx , roomID , err )
179+ continue
196180 }
197- if * content . Hidden {
181+ if hidden {
198182 if r .meta .HiddenRooms == nil {
199183 r .meta .HiddenRooms = make (map [id.RoomID ]bool )
200184 }
201185 r .meta .HiddenRooms [roomID ] = true
202- if err = r .handleLeftRoom (ctx , roomID ); err != nil {
203- return nil , err
204- }
186+ logRoomError (ctx , roomID , r .handleLeftRoom (ctx , roomID ))
205187 } else {
206188 delete (r .meta .HiddenRooms , roomID )
207189 if resp .Rooms .Join [roomID ] == nil && resp .Rooms .Invite [roomID ] == nil {
@@ -211,10 +193,30 @@ func (r *RedditClient) applyHiddenChatData(ctx context.Context, resp *redditchat
211193 }
212194 }
213195 }
214- return unhidden , nil
196+ return unhidden
197+ }
198+
199+ func parseHiddenChat (evt * event.Event ) (bool , error ) {
200+ var content struct {
201+ Hidden * bool `json:"hidden"`
202+ }
203+ raw , err := json .Marshal (& evt .Content )
204+ if err != nil {
205+ return false , err
206+ }
207+ if err = json .Unmarshal (raw , & content ); err != nil {
208+ return false , err
209+ }
210+ if content .Hidden == nil {
211+ return false , errors .New ("reddit hidden chat state has no hidden flag" )
212+ }
213+ return * content .Hidden , nil
215214}
216215
217216func (r * RedditClient ) handleRoomUpdate (ctx context.Context , roomID id.RoomID , joined * mautrix.SyncJoinedRoom ) error {
217+ if joined == nil {
218+ return errors .New ("reddit sync contains a null room" )
219+ }
218220 state , portalKey , err := r .resolveRoomState (ctx , roomID , joined )
219221 if err != nil {
220222 return err
@@ -224,11 +226,12 @@ func (r *RedditClient) handleRoomUpdate(ctx context.Context, roomID id.RoomID, j
224226 r .userLogin .QueueRemoteEvent (resync )
225227
226228 for _ , evt := range joined .Timeline .Events {
227- if err = r .handleTimelineEvent (ctx , portalKey , evt ); err != nil {
228- return err
229+ if err = r .handleTimelineEvent (ctx , portalKey , evt ); err != nil && ctx . Err () == nil {
230+ zerolog . Ctx ( ctx ). Err ( err ). Stringer ( "room_id" , roomID ). Stringer ( "event_id" , evt . ID ). Msg ( "Failed to handle Reddit event, skipping" )
229231 }
230232 }
231- return r .handleEphemeralEvents (ctx , portalKey , joined .Ephemeral .Events )
233+ r .handleEphemeralEvents (ctx , portalKey , joined .Ephemeral .Events )
234+ return nil
232235}
233236
234237func (r * RedditClient ) handleInvitedRoom (ctx context.Context , roomID id.RoomID , invited * mautrix.SyncInvitedRoom ) error {
0 commit comments