Skip to content

2.0.0-rc.3 | projection leaks after flush while in transition  #3074

Closed

Description

@mizulu

Describe the bug

A projection of count() leaks the set value after a flush while still being in the transition
while count() still shows the uncommitted value.

logs

[log] action:start
[log] p.a 0
[log] count 0
[log] p.a 5       <--- leak
[log] count 0   <-- no leak 
[log] action:done

Your Example Website or App

https://s.olid.uk/id/ij_hsNqcRVKCqfWdatIU0Q

Steps to Reproduce the Bug or Issue

playground

Expected behavior

expecting the expected. no leak

Screenshots or Videos

Image

Platform

.

Additional context

import { render, untrack } from '@solidjs/web';
import { createSignal, createProjection, action, flush } from 'solid-js';

export default function App() {
  const [count, setCount] = createSignal(0);



  const p = createProjection(() => {
    return { a: count() }
  }, {})


  const act = action(function* () {
    console.log("action:start")
    yield new Promise(r => setTimeout(r, 3000))
    console.log("action:done")

  })

  setTimeout(() => {
    untrack(() => {

      act() // holds transition for 3s


      setCount(v => v + 5)
      console.log("p.a", p.a)        // 0
      console.log("count", count())  // 0 

      flush()

      console.log("p.a", p.a)        // 5      
      console.log("count", count())  // 0 

    })
  })



  return (
    <div class="p-2">
      Projection leak after flush, see logs
    </div>
  );
}


if (typeof document !== 'undefined') {
  render(() => <App />, document.getElementById('root')!);
}

Activity

  1. changed the title [-]2.0.0.rc3 | projection acts[/-] [+]2.0.0.rc3 | projection leaks after flush while in transition [/+] on Aug 27, 2026
  2. changed the title [-]2.0.0.rc3 | projection leaks after flush while in transition [/-] [+]2.0.0-rc.3 | projection leaks after flush while in transition [/+] on Aug 27, 2026
  3. ryansolid commented on Aug 27, 2026

    @ryansolid
    Member

    Fixed in 0e37f90 — thanks for the tight repro, and for filing #3075 alongside it: they turned out to be the two halves of the same isolation gap, in opposite directions.

    What was happening: a projection recompute commits its output through the store's adoption channel, which swaps the committed backing eagerly by contract (that's what makes reconcile immediately visible outside transitions). Node-level notifications ride core setSignal and park correctly under the transition — which is why subscribed effects were properly held — but an untracked read serves the backing directly, so p.a showed the speculative value after flush() while count() (whose write parked in the signal's pending slot) correctly stayed committed.

    The fix stages a held view: when a projection adoption lands under a live transition, the pre-hold committed backing stays servable, and committed-visibility readers (untracked reads included) keep seeing it until the transition commits. Speculative readers — the derive's own draft, computeds recomputing inside the transaction, and latest() — see the adopted backing, so the transaction still observes itself consistently.

    Your repro's log sequence now reads p.a 0 / count 0 after the flush, and both flip to 5 when the action's transition lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions