@@ -200,13 +200,16 @@ DeferredWorkTimer::Ticket DeferredWorkTimer::addPendingWork(WorkType type, VM& v
200200 Ticket ticket = ticketData.ptr ();
201201
202202 dataLogLnIf (DeferredWorkTimerInternal::verbose, " Adding new pending ticket: " , RawPointer (ticket));
203+
204+ // Always add to m_pendingTickets so GC can see and cleanup invalid tickets
205+ auto result = m_pendingTickets.add (ticketData);
206+ RELEASE_ASSERT (result.isNewEntry );
207+
208+ // If custom event loop hook is set, also pass a reference to it
203209 if (onAddPendingWork) {
204- onAddPendingWork (WTFMove (ticketData), type);
205- } else {
206- auto result = m_pendingTickets.add (WTFMove (ticketData));
207- RELEASE_ASSERT (result.isNewEntry );
210+ Ref<TicketData> ticketForCustomEventLoop = *ticket;
211+ onAddPendingWork (WTFMove (ticketForCustomEventLoop), type);
208212 }
209-
210213
211214 return ticket;
212215}
@@ -303,22 +306,24 @@ void DeferredWorkTimer::cancelPendingWork(VM& vm)
303306 };
304307
305308 bool needToFire = false ;
306- for (auto & ticket : m_pendingTickets) {
309+
310+ // Use removeIf to safely remove invalid tickets during iteration
311+ m_pendingTickets.removeIf ([&](auto & ticket) {
307312 if (ticket->isCancelled () || !isValid (ticket)) {
308313 // At this point, no one can visit or need the dependencies.
309314 // So, they are safe to clear here for better debugging and testing.
310315 ticket->cancelAndClear ();
311316 needToFire = true ;
312317
318+ // Notify custom event loop to remove from its storage too
313319 if (onCancelPendingWork) {
314320 onCancelPendingWork (&ticket.get ());
315321 }
316- }
317- }
318322
319- if (onCancelPendingWork) {
320- return ;
321- }
323+ return true ; // Remove from m_pendingTickets
324+ }
325+ return false ; // Keep in m_pendingTickets
326+ });
322327
323328 // GC can be triggered before an invalid and scheduled ticket is fired. In that case,
324329 // we also need to remove the corresponding pending task. Since doWork handles all cases
0 commit comments