Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self resolving vows cause infinite loop #9560

Closed
mhofman opened this issue Jun 21, 2024 · 0 comments · Fixed by #9561
Closed

Self resolving vows cause infinite loop #9560

mhofman opened this issue Jun 21, 2024 · 0 comments · Fixed by #9561
Labels
bug Something isn't working

Comments

@mhofman
Copy link
Member

mhofman commented Jun 21, 2024

Describe the bug

Resolving a vow to itself, either directly or through intermediate promises and/or other vows, result in an infinite loop through shortening.

Unlike promise self resolution which at best result in a data lock when awaited, the consequence here is a full livelock (no other work ends up processed while in the infinite loop)

To Reproduce

test.failing('vow self resolution', async t => {
  const zone = makeHeapZone();
  const { watch, when, makeVowKit } = prepareVowTools(zone);

  // A direct self vow resolution
  const { vow: vow1, resolver: resolver1 } = makeVowKit();
  resolver1.resolve(vow1);

  // A self vow resolution through promise
  const { vow: vow2, resolver: resolver2 } = makeVowKit();
  const vow2P = Promise.resolve(vow2);
  resolver2.resolve(vow2P);

  // A 2 vow loop
  const { vow: vow3, resolver: resolver3 } = makeVowKit();
  const { vow: vow4, resolver: resolver4 } = makeVowKit();
  resolver3.resolve(vow4);
  resolver4.resolve(vow3);

  // A head pointing to a 2 vow loop (a lasso?)
  const { vow: vow5, resolver: resolver5 } = makeVowKit();
  resolver5.resolve(vow4);

  const turnTimeout = async n => {
    if (n > 0) {
      return Promise.resolve(n - 1).then(turnTimeout);
    }

    return 'timeout';
  };

  /**
   * @param {number} n
   * @param {Promise<any>} promise
   */
  const raceTurnTimeout = async (n, promise) =>
    Promise.race([promise, turnTimeout(n)]);

  await t.throwsAsync(raceTurnTimeout(20, when(watch(vow1))));

  await t.throwsAsync(raceTurnTimeout(20, when(watch(vow2))));

  await t.throwsAsync(raceTurnTimeout(20, when(watch(vow3))));

  await t.throwsAsync(raceTurnTimeout(20, when(watch(vow5))));
});

Expected behavior

No infinite loop

Platform Environment

Additional context

Screenshots

@mhofman mhofman added the bug Something isn't working label Jun 21, 2024
@mergify mergify bot closed this as completed in #9561 Jun 22, 2024
@mergify mergify bot closed this as completed in a4f86eb Jun 22, 2024
mhofman added a commit that referenced this issue Jun 22, 2024
closes: #9560

## Description
Add a weak set of previously seen vowV0 payloads when shortening during `watch` or `when`

### Security Considerations
None

### Scaling Considerations
Potential churn in storage just for shortening loop detection. Every `watch` will cause a new WeakSetStore to be allocated.

### Documentation Considerations
None

### Testing Considerations
New unit tests

### Upgrade Considerations
To avoid state migration concerns, this needs to be deployed before we start using `watch` more broadly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant