Bladeren bron

Merge pull request #6401 from afshin/poll-when-fix

Fix file browser model autostart, simplify poll start logic.
Steven Silvester 6 jaren geleden
bovenliggende
commit
7fb34cdb18

+ 6 - 0
buildutils/README.md

@@ -0,0 +1,6 @@
+# @jupyterlab/buildutils
+
+A JupyterLab package which provides utility functions that are used to compile
+and build JupyterLab.
+
+This package is only intended for use within Node.js environments.

+ 1 - 1
docs/source/user/extensions.rst

@@ -352,7 +352,7 @@ The following configurations may be present in this file:
 2. ``disabledExtensions`` controls which extensions should not load at all.
 3. ``deferredExtensions`` controls which extensions should not load until
    they are required by something, irrespective of whether they set
-   ``autostart`` to ``true``.
+   ``autoStart`` to ``true``.
 
 The value for the ``disabledExtensions`` and ``deferredExtensions`` fields
 are an array of strings. The following sequence of checks are performed

+ 5 - 2
packages/coreutils/README.md

@@ -1,4 +1,7 @@
 # @jupyterlab/coreutils
 
-A JupyterLab package which provides utility functions that are widely used across many
-of the `@jupyterlab` packages. This includes (among other things) functions for manipulating paths, urls, and the notebook format.
+A JupyterLab package which provides utility functions that are widely used
+across many of the `@jupyterlab` packages. This includes (among other things)
+functions for manipulating paths, urls, and the notebook format.
+
+This package is intended for use within both Node.js and browser environments.

+ 1 - 1
packages/coreutils/src/activitymonitor.ts

@@ -73,7 +73,7 @@ export class ActivityMonitor<Sender, Args> implements IDisposable {
     }, this._timeout);
   }
 
-  private _timer = -1;
+  private _timer: any = -1;
   private _timeout = -1;
   private _sender: Sender;
   private _args: Args;

+ 32 - 38
packages/coreutils/src/poll.ts

@@ -7,6 +7,22 @@ import { IDisposable } from '@phosphor/disposable';
 
 import { ISignal, Signal } from '@phosphor/signaling';
 
+/**
+ * A function to defer an action immediately.
+ */
+const defer =
+  typeof requestAnimationFrame === 'function'
+    ? requestAnimationFrame
+    : setImmediate;
+
+/**
+ * A function to cancel a deferred action.
+ */
+const cancel: (timeout: any) => void =
+  typeof cancelAnimationFrame === 'function'
+    ? cancelAnimationFrame
+    : clearImmediate;
+
 /**
  * A readonly poll that calls an asynchronous function with each tick.
  *
@@ -112,9 +128,7 @@ export namespace IPoll {
     | 'resolved'
     | 'standby'
     | 'started'
-    | 'stopped'
-    | 'when-rejected'
-    | 'when-resolved';
+    | 'stopped';
 
   /**
    * Definition of poll state at any given time.
@@ -183,23 +197,9 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
     };
     this.name = options.name || Private.DEFAULT_NAME;
 
-    // Schedule poll ticks after `when` promise is settled.
-    (options.when || Promise.resolve())
-      .then(_ => {
-        if (this.isDisposed) {
-          return;
-        }
-
-        void this.schedule({ phase: 'when-resolved' });
-      })
-      .catch(reason => {
-        if (this.isDisposed) {
-          return;
-        }
-
-        console.warn(`Poll (${this.name}) started despite rejection.`, reason);
-        void this.schedule({ phase: 'when-rejected' });
-      });
+    if ('auto' in options ? options.auto : true) {
+      defer(() => void this.start());
+    }
   }
 
   /**
@@ -317,7 +317,7 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
    */
   refresh(): Promise<void> {
     return this.schedule({
-      cancel: last => last.phase === 'refreshed',
+      cancel: ({ phase }) => phase === 'refreshed',
       interval: Poll.IMMEDIATE,
       phase: 'refreshed'
     });
@@ -345,13 +345,6 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
       return;
     }
 
-    // The `when` promise in the constructor options acts as a gate.
-    if (this.state.phase === 'constructed') {
-      if (next.phase !== 'when-rejected' && next.phase !== 'when-resolved') {
-        await this.tick;
-      }
-    }
-
     // Check if the phase transition should be canceled.
     if (next.cancel && next.cancel(this.state)) {
       return;
@@ -373,7 +366,7 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
 
     // Clear the schedule if possible.
     if (last.interval === Poll.IMMEDIATE) {
-      cancelAnimationFrame(this._timeout);
+      cancel(this._timeout);
     } else {
       clearTimeout(this._timeout);
     }
@@ -393,7 +386,7 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
     };
     this._timeout =
       state.interval === Poll.IMMEDIATE
-        ? requestAnimationFrame(execute)
+        ? defer(execute)
         : state.interval === Poll.NEVER
         ? -1
         : setTimeout(execute, state.interval);
@@ -406,7 +399,8 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
    */
   start(): Promise<void> {
     return this.schedule({
-      cancel: last => last.phase !== 'standby' && last.phase !== 'stopped',
+      cancel: ({ phase }) =>
+        phase !== 'constructed' && phase !== 'standby' && phase !== 'stopped',
       interval: Poll.IMMEDIATE,
       phase: 'started'
     });
@@ -419,7 +413,7 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
    */
   stop(): Promise<void> {
     return this.schedule({
-      cancel: last => last.phase === 'stopped',
+      cancel: ({ phase }) => phase === 'stopped',
       interval: Poll.NEVER,
       phase: 'stopped'
     });
@@ -477,7 +471,7 @@ export class Poll<T = any, U = any, V extends string = 'standby'>
   private _state: IPoll.State<T, U, V>;
   private _tick = new PromiseDelegate<this>();
   private _ticked = new Signal<this, IPoll.State<T, U, V>>(this);
-  private _timeout = -1;
+  private _timeout: any = -1;
 }
 
 /**
@@ -512,6 +506,11 @@ export namespace Poll {
    * @typeparam V - The type to extend the phases supported by a poll.
    */
   export interface IOptions<T, U, V extends string> {
+    /**
+     * Whether to begin polling automatically; defaults to `true`.
+     */
+    auto?: boolean;
+
     /**
      * A factory function that is passed a poll tick and returns a poll promise.
      */
@@ -539,11 +538,6 @@ export namespace Poll {
      * tick execution, but may be called by clients as well.
      */
     standby?: Standby | (() => boolean | Standby);
-
-    /**
-     * If set, a promise which must resolve (or reject) before polling begins.
-     */
-    when?: Promise<any>;
   }
   /**
    * An interval value that indicates the poll should tick immediately.

+ 1 - 0
packages/coreutils/src/ratelimiter.ts

@@ -25,6 +25,7 @@ export abstract class RateLimiter<T, U> implements IRateLimiter<T, U> {
   constructor(fn: () => T | Promise<T>, limit = 500) {
     this.limit = limit;
     this.poll = new Poll({
+      auto: false,
       factory: async () => await fn(),
       frequency: { backoff: false, interval: Poll.NEVER, max: Poll.NEVER },
       standby: 'never'

+ 2 - 1
packages/coreutils/tsconfig.json

@@ -3,7 +3,8 @@
   "compilerOptions": {
     "outDir": "lib",
     "rootDir": "src",
-    "module": "commonjs"
+    "module": "commonjs",
+    "types": ["node"]
   },
   "files": ["src/plugin-schema.json"],
   "include": ["src/*"]

+ 8 - 4
packages/services/src/kernel/manager.ts

@@ -39,6 +39,7 @@ export class KernelManager implements Kernel.IManager {
 
     // Start model and specs polling with exponential backoff.
     this._pollModels = new Poll({
+      auto: false,
       factory: () => this.requestRunning(),
       frequency: {
         interval: 10 * 1000,
@@ -46,10 +47,10 @@ export class KernelManager implements Kernel.IManager {
         max: 300 * 1000
       },
       name: `@jupyterlab/services:KernelManager#models`,
-      standby: options.standby || 'when-hidden',
-      when: this.ready
+      standby: options.standby || 'when-hidden'
     });
     this._pollSpecs = new Poll({
+      auto: false,
       factory: () => this.requestSpecs(),
       frequency: {
         interval: 61 * 1000,
@@ -57,8 +58,11 @@ export class KernelManager implements Kernel.IManager {
         max: 300 * 1000
       },
       name: `@jupyterlab/services:KernelManager#specs`,
-      standby: options.standby || 'when-hidden',
-      when: this.ready
+      standby: options.standby || 'when-hidden'
+    });
+    void this.ready.then(() => {
+      void this._pollModels.start();
+      void this._pollSpecs.start();
     });
   }
 

+ 8 - 4
packages/services/src/session/manager.ts

@@ -41,6 +41,7 @@ export class SessionManager implements Session.IManager {
 
     // Start model and specs polling with exponential backoff.
     this._pollModels = new Poll({
+      auto: false,
       factory: () => this.requestRunning(),
       frequency: {
         interval: 10 * 1000,
@@ -48,10 +49,10 @@ export class SessionManager implements Session.IManager {
         max: 300 * 1000
       },
       name: `@jupyterlab/services:SessionManager#models`,
-      standby: options.standby || 'when-hidden',
-      when: this.ready
+      standby: options.standby || 'when-hidden'
     });
     this._pollSpecs = new Poll({
+      auto: false,
       factory: () => this.requestSpecs(),
       frequency: {
         interval: 61 * 1000,
@@ -59,8 +60,11 @@ export class SessionManager implements Session.IManager {
         max: 300 * 1000
       },
       name: `@jupyterlab/services:SessionManager#specs`,
-      standby: options.standby || 'when-hidden',
-      when: this.ready
+      standby: options.standby || 'when-hidden'
+    });
+    void this.ready.then(() => {
+      void this._pollModels.start();
+      void this._pollSpecs.start();
     });
   }
 

+ 5 - 2
packages/services/src/terminal/manager.ts

@@ -44,6 +44,7 @@ export class TerminalManager implements TerminalSession.IManager {
 
     // Start polling with exponential backoff.
     this._pollModels = new Poll({
+      auto: false,
       factory: () => this.requestRunning(),
       frequency: {
         interval: 10 * 1000,
@@ -51,8 +52,10 @@ export class TerminalManager implements TerminalSession.IManager {
         max: 300 * 1000
       },
       name: `@jupyterlab/services:TerminalManager#models`,
-      standby: options.standby || 'when-hidden',
-      when: this.ready
+      standby: options.standby || 'when-hidden'
+    });
+    void this.ready.then(() => {
+      void this._pollModels.start();
     });
   }
 

+ 53 - 74
tests/test-coreutils/src/poll.spec.ts

@@ -17,44 +17,50 @@ describe('Poll', () => {
   describe('#constructor()', () => {
     it('should create a poll', () => {
       poll = new Poll({
+        auto: false,
         factory: () => Promise.resolve(),
-        name: '@jupyterlab/test-coreutils:Poll#constructor()-1',
-        when: new Promise(() => undefined) // Never.
+        name: '@jupyterlab/test-coreutils:Poll#constructor()-1'
       });
       expect(poll).to.be.an.instanceof(Poll);
     });
 
-    it('should be `when-resolved` after `when` resolves', async () => {
-      const promise = Promise.resolve();
+    it('should start polling automatically', async () => {
+      const expected = 'started resolved';
+      const ticker: IPoll.Phase<any>[] = [];
       poll = new Poll({
-        factory: () => Promise.resolve(),
         name: '@jupyterlab/test-coreutils:Poll#constructor()-2',
-        when: promise
+        frequency: { interval: 100 },
+        factory: () => Promise.resolve()
+      });
+      poll.ticked.connect((_, tick) => {
+        ticker.push(tick.phase);
       });
       expect(poll.state.phase).to.equal('constructed');
-      await promise;
-      expect(poll.state.phase).to.equal('when-resolved');
+      await poll.tick;
+      expect(poll.state.phase).to.equal('started');
+      await poll.tick;
+      expect(poll.state.phase).to.equal('resolved');
+      expect(ticker.join(' ')).to.equal(expected);
     });
 
-    it('should be `when-rejected` after `when` rejects', async () => {
-      const promise = Promise.reject();
+    it('should not poll if `auto` is set to false', async () => {
+      const expected = '';
+      const ticker: IPoll.Phase<any>[] = [];
       poll = new Poll({
-        factory: () => Promise.resolve(),
-        name: '@jupyterlab/test-coreutils:Poll#constructor()-3',
-        when: promise
+        auto: false,
+        name: '@jupyterlab/test-coreutils:Poll#constructor()-2',
+        frequency: { interval: 100 },
+        factory: () => Promise.resolve()
+      });
+      poll.ticked.connect((_, tick) => {
+        ticker.push(tick.phase);
       });
       expect(poll.state.phase).to.equal('constructed');
-      await promise.catch(() => undefined);
-      expect(poll.state.phase).to.equal('when-rejected');
+      await sleep(250); // Sleep for longer than the interval.
+      expect(ticker.join(' ')).to.equal(expected);
     });
 
     describe('#options.frequency', () => {
-      let poll: Poll;
-
-      afterEach(() => {
-        poll.dispose();
-      });
-
       it('should set frequency interval', () => {
         const interval = 9000;
         poll = new Poll({
@@ -131,13 +137,11 @@ describe('Poll', () => {
       const name = '@jupyterlab/test-coreutils:Poll#name-1';
       poll = new Poll({ factory, name });
       expect(poll.name).to.equal(name);
-      poll.dispose();
     });
 
     it('should default to `unknown`', () => {
       poll = new Poll({ factory: () => Promise.resolve() });
       expect(poll.name).to.equal('unknown');
-      poll.dispose();
     });
   });
 
@@ -171,17 +175,19 @@ describe('Poll', () => {
   describe('#tick', () => {
     it('should resolve after a tick', async () => {
       poll = new Poll({
+        auto: false,
         factory: () => Promise.resolve(),
         frequency: { interval: 200, backoff: false },
         name: '@jupyterlab/test-coreutils:Poll#tick-1'
       });
-      const expected = 'when-resolved resolved';
+      const expected = 'started resolved resolved';
       const ticker: IPoll.Phase<any>[] = [];
       const tock = (poll: Poll) => {
         ticker.push(poll.state.phase);
         poll.tick.then(tock).catch(() => undefined);
       };
       void poll.tick.then(tock);
+      void poll.start();
       await sleep(250); // Sleep for longer than the interval.
       expect(ticker.join(' ')).to.equal(expected);
     });
@@ -226,39 +232,6 @@ describe('Poll', () => {
   });
 
   describe('#ticked', () => {
-    it('should emit when the poll ticks after `when` resolves', async () => {
-      const expected = 'when-resolved resolved';
-      const ticker: IPoll.Phase<any>[] = [];
-      poll = new Poll<void, void>({
-        factory: () => Promise.resolve(),
-        frequency: { interval: 200, backoff: false },
-        name: '@jupyterlab/test-coreutils:Poll#ticked-1'
-      });
-      poll.ticked.connect(() => {
-        ticker.push(poll.state.phase);
-      });
-      await sleep(250); // Sleep for longer than the interval.
-      expect(ticker.join(' ')).to.equal(expected);
-    });
-
-    it('should emit when the poll ticks after `when` rejects', async () => {
-      const expected = 'when-rejected resolved';
-      const ticker: IPoll.Phase<any>[] = [];
-      const promise = Promise.reject();
-      poll = new Poll({
-        factory: () => Promise.resolve(),
-        frequency: { interval: 200, backoff: false },
-        name: '@jupyterlab/test-coreutils:Poll#ticked-2',
-        when: promise
-      });
-      poll.ticked.connect(() => {
-        ticker.push(poll.state.phase);
-      });
-      await promise.catch(() => undefined);
-      await sleep(250); // Sleep for longer than the interval.
-      expect(ticker.join(' ')).to.equal(expected);
-    });
-
     it('should emit a tick identical to the poll state', async () => {
       poll = new Poll<void, void>({
         factory: () => Promise.resolve(),
@@ -295,8 +268,8 @@ describe('Poll', () => {
   });
 
   describe('#refresh()', () => {
-    it('should refresh the poll when the poll is ready', async () => {
-      const expected = 'when-resolved refreshed resolved';
+    it('should refresh the poll, superseding `started`', async () => {
+      const expected = 'refreshed resolved';
       const ticker: IPoll.Phase<any>[] = [];
       poll = new Poll({
         name: '@jupyterlab/test-coreutils:Poll#refresh()-1',
@@ -315,7 +288,7 @@ describe('Poll', () => {
     });
 
     it('should be safe to call multiple times', async () => {
-      const expected = 'when-resolved refreshed resolved';
+      const expected = 'started resolved refreshed resolved';
       const ticker: IPoll.Phase<any>[] = [];
       poll = new Poll({
         name: '@jupyterlab/test-coreutils:Poll#refresh()-2',
@@ -327,7 +300,9 @@ describe('Poll', () => {
       });
       expect(poll.state.phase).to.equal('constructed');
       await poll.tick;
-      expect(poll.state.phase).to.equal('when-resolved');
+      expect(poll.state.phase).to.equal('started');
+      await poll.tick;
+      expect(poll.state.phase).to.equal('resolved');
       await poll.refresh();
       expect(poll.state.phase).to.equal('refreshed');
       await poll.refresh();
@@ -342,7 +317,7 @@ describe('Poll', () => {
 
   describe('#start()', () => {
     it('should start the poll if it is stopped', async () => {
-      const expected = 'when-resolved stopped started resolved';
+      const expected = 'stopped started resolved';
       const ticker: IPoll.Phase<any>[] = [];
       poll = new Poll({
         name: '@jupyterlab/test-coreutils:Poll#start()-1',
@@ -352,8 +327,6 @@ describe('Poll', () => {
       poll.ticked.connect((_, tick) => {
         ticker.push(tick.phase);
       });
-      await poll.tick;
-      expect(poll.state.phase).to.equal('when-resolved');
       await poll.stop();
       expect(poll.state.phase).to.equal('stopped');
       await poll.start();
@@ -364,9 +337,10 @@ describe('Poll', () => {
     });
 
     it('be safe to call multiple times and no-op if unnecessary', async () => {
-      const expected = 'when-resolved resolved stopped started resolved';
+      const expected = 'started resolved stopped started resolved';
       const ticker: IPoll.Phase<any>[] = [];
       poll = new Poll({
+        auto: false,
         name: '@jupyterlab/test-coreutils:Poll#start()-2',
         frequency: { interval: 100 },
         factory: () => Promise.resolve()
@@ -376,11 +350,11 @@ describe('Poll', () => {
       });
       expect(poll.state.phase).to.equal('constructed');
       await poll.start();
-      expect(poll.state.phase).to.equal('when-resolved');
+      expect(poll.state.phase).to.equal('started');
       await poll.start();
-      expect(poll.state.phase).to.equal('when-resolved');
+      expect(poll.state.phase).to.equal('started');
       await poll.start();
-      expect(poll.state.phase).to.equal('when-resolved');
+      expect(poll.state.phase).to.equal('started');
       await poll.tick;
       expect(poll.state.phase).to.equal('resolved');
       await poll.stop();
@@ -395,9 +369,10 @@ describe('Poll', () => {
 
   describe('#stop()', () => {
     it('should stop the poll if it is active', async () => {
-      const expected = 'when-resolved stopped started resolved';
+      const expected = 'started stopped started resolved';
       const ticker: IPoll.Phase<any>[] = [];
       poll = new Poll({
+        auto: false,
         name: '@jupyterlab/test-coreutils:Poll#stop()-1',
         frequency: { interval: 100 },
         factory: () => Promise.resolve()
@@ -405,7 +380,8 @@ describe('Poll', () => {
       poll.ticked.connect((_, tick) => {
         ticker.push(tick.phase);
       });
-      expect(poll.state.phase).to.equal('constructed');
+      await poll.start();
+      expect(poll.state.phase).to.equal('started');
       await poll.stop();
       expect(poll.state.phase).to.equal('stopped');
       await poll.start();
@@ -416,9 +392,10 @@ describe('Poll', () => {
     });
 
     it('be safe to call multiple times', async () => {
-      const expected = 'when-resolved stopped started resolved';
+      const expected = 'started stopped started resolved';
       const ticker: IPoll.Phase<any>[] = [];
       poll = new Poll({
+        auto: false,
         name: '@jupyterlab/test-coreutils:Poll#stop()-2',
         frequency: { interval: 100 },
         factory: () => Promise.resolve()
@@ -427,6 +404,8 @@ describe('Poll', () => {
         ticker.push(tick.phase);
       });
       expect(poll.state.phase).to.equal('constructed');
+      await poll.start();
+      expect(poll.state.phase).to.equal('started');
       await poll.stop();
       expect(poll.state.phase).to.equal('stopped');
       await poll.stop();
@@ -450,7 +429,7 @@ describe('Poll', () => {
       });
       expect(poll.state.phase).to.equal('constructed');
       await poll.tick;
-      expect(poll.state.phase).to.equal('when-resolved');
+      expect(poll.state.phase).to.equal('started');
       await poll.tick;
       expect(poll.state.phase).to.equal('resolved');
       await poll.schedule({ phase: 'refreshed' });
@@ -466,7 +445,7 @@ describe('Poll', () => {
       });
       expect(poll.state.phase).to.equal('constructed');
       await poll.tick;
-      expect(poll.state.phase).to.equal('when-resolved');
+      expect(poll.state.phase).to.equal('started');
       await poll.tick;
       expect(poll.state.phase).to.equal('resolved');
       await poll.schedule();
@@ -482,7 +461,7 @@ describe('Poll', () => {
       });
       expect(poll.state.phase).to.equal('constructed');
       await poll.tick;
-      expect(poll.state.phase).to.equal('when-resolved');
+      expect(poll.state.phase).to.equal('started');
       await poll.tick;
       expect(poll.state.phase).to.equal('resolved');
       await poll.schedule();