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

Implement a scheduledDelayedFunction #564

Closed
dubzzz opened this issue Mar 20, 2020 · 5 comments · Fixed by #566
Closed

Implement a scheduledDelayedFunction #564

dubzzz opened this issue Mar 20, 2020 · 5 comments · Fixed by #566

Comments

@dubzzz
Copy link
Owner

dubzzz commented Mar 20, 2020

🚀 Feature Request

Similar to scheduledFunction but the call to the function itself is also delayed. It is just a syntaxic sugar but it might be used for lots of cases dealing with race conditions.

Motivation

Today scheduledFunction, barely mimic real worl problem we can have when running asynchronous queries on a distributed environnement. When calling a function wrapped into scheduledFunction, the function itself is immediately called. In a context where this function has been mocked, it would be pretty useful to introduce (a scheduled) delay between the moment when the call is triggered and the moment when the call is taken into account by the mock (a bit like a call to server).

@CMCDragonkai
Copy link

We just hit this problem and ended up creating our own gated way of executing the delayed scheduled functions.

But how was this actually resolved? What is the right way to do this?

Our attempt:

  const gateFactory = (s: fc.Scheduler) => {
    const scheduledStarter = s.scheduleFunction(async () => {});
    return async <T>(f: () => Promise<T>) => {
      await scheduledStarter();
      return await f();
    };
  };

Example of how it was used:

      await fc.assert(
        fc
          .asyncProperty(fc.scheduler(), async (s) => {
            await efs.mkdir('dir');
            const gate = gateFactory(s);
            const prom = Promise.allSettled([
              gate(() => efs.mknod(path1, constants.S_IFREG, 0, 0)),
              gate(() => efs.mknod(path1, constants.S_IFREG, 0, 0)),
              gate(() => efs.open(path1, constants.O_RDWR | constants.O_CREAT)),
              gate(() => efs.open(path1, constants.O_RDWR | constants.O_CREAT)),
              gate(() => efs.mkdir(path1)),
              gate(() => efs.mkdir(path1)),
            ]);
            await s.waitAll();
            const results = await prom;
            results.map((item) => {
              if (item.status !== 'fulfilled') {
                // Should fail as a normal FS error
                expectReason(item, ErrorEncryptedFSError);
              }
            });
            // Should have at least 1 success
            expect(results.some((item) => item.status === 'fulfilled')).toBe(
              true,
            );
          })
          .afterEach(async () => {
            // Cleaning up
            await efs.rmdir('dir', { recursive: true });
          }),
        { numRuns: 50 },
      );

@dubzzz
Copy link
Owner Author

dubzzz commented Aug 19, 2022

If I understand well your nee: you attempt to fire the function passed to gate in any random ordering.

Given the code you wrote, you may have executions like:

gantt 
dateFormat HH:mm
axisFormat %H:%M
g1: m1, 00:06, 4min
g2: m2, 00:03, 2min
g3: m3, 00:00, 10min
Loading

Note that g1 stands for efs.mknod(path1, constants.S_IFREG, 0, 0).

In other words, it will fire calls to gX in random order (controlled by s). But will not wait for previous one to end before starting the next one.

The snippet of gateFactory could be enhanced to had more context to the error messages in case of failures with something like:

  const gateFactory = (s: fc.Scheduler) => {
    const scheduledStarter = s.scheduleFunction(async (_label: string) => {});
    return async <T>(label: string, f: () => Promise<T>) => {
      await scheduledStarter(label);
      return await f();
    };
  };

Or just:

  const gateFactory = (s: fc.Scheduler) => {
    return async <T>(label: string, f: () => Promise<T>) => {
      await s.schedule(Promise.resolve(label));
      return await f();
    };
  };

@CMCDragonkai
Copy link

Yep that's precisely it. We started using your scheduleCall helper. I wonder if that should be part of the library so you can make that clearer. We are testing the result of side-effects, and not really the result value of our promises.

Also I noticed scheduledModelRun was also referenced in this issue. But there's no docs on how to use it.

@dubzzz
Copy link
Owner Author

dubzzz commented Aug 19, 2022

So far scheduleCall has not been added to the official documentation as there are many ways to build one. More precisely some may want to schedule a call and fully wait the end of it before moving to something else, while others (like your case) may just want to delay the initial trigger, and also other usages. For that reason, it is still not officially part of the official API.

Regarding the second one, documentation is indeed partially missing. You may want to refer to the examples: https://github.com/dubzzz/fast-check/blob/86164f1b12f010f25fbd16a5acc63bd1c3348dc8/examples/005-race/todolist/main.spec.tsx as https://github.com/dubzzz/fast-check/blob/e7b361ffe13ff22a458d25431788e3caa359646a/packages/fast-check/documentation/RaceConditions.md does not give that much details at the moment.

@CMCDragonkai
Copy link

I see, but I fee like scheduleDelayedFunction would be quite useful to automate this. I pretty much use this soley when I use the scheduler.

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

Successfully merging a pull request may close this issue.

2 participants