Managing resource design pattern

JS resource object

We will start by defining a resource as an object which is loaded into the memory of our process using an async operation, this operation may take a relatively long period of time. This resource object will be in use very often, computing it from scratch each time will be a huge waste, this can cause our service to be significantly underutilized for no good reason.

This may resemble the Singelton pattern object initializing in a multi-threaded environment (this is a question that is often used in job interviews) – which one of the threads will load the resource and only once.

When creating the resource object one may check if the static member is not initialized and initialize the member if needed moving on thinking he just successfully cleared this issue.

Let’s review the following scenario: the service is highly in use in our server, it gets hits in a ratio of 5/sec, the init resource function will run for ~ 5 sec’. The first request comes in and fires the async ‘getInstance’ function since it is an async request the event loop will now stop the function execution and resume once the async function has been resolved – in our example after 5 seconds.

The resource code

export class Resource {
  static instance;

 constructor(... args) {
  }

  static async getInstance() {
    if (!Resource.instance){ Resource.instance =
      await ResourceFactory.getInstance();}
    else {
      console.log('no need to init resource we have it')
    }
    return Resource.instance;
  }
}

export class ResourceFactory {
  static getInstance(){
    return new Promise((resolve,reject)=>{
      console.log('init resource')
      setTimeout(()=>{
        console.log('resolving resource');
        resolve(true)
      },5000);
    })
  }
}

We can now simulate this scenario from a user perspective

let i = 0;
const intervalId = setInterval(async () => {
  const resource = await Resource.getInstance();
  console.log('resource is init');
  if (i === 50) clearInterval(intervalId);
  i++;

}, 200);
init resource
init resource
init resource
init resource
resolving resource
resource is init
no need to init resource we have it
resource is init
resolving resource
resource is init
no need to init resource we have it
resource is init
resolving resource
resource is init
.......

The output of the function will clearly indicate that we are initing the resource much more than the desired once. Heres the problem, during this 5 sec of initialization we will receive approximately 25 requests, Each of those times the if statement will have a false evaluation and a new request will fire summing up to more then 25 times. after at least 5 sec from the beginning, we will finally receive an instance and the if statement will finally evaluate to false as we desired, still, we have many new init request in the pipeline that we do not need anymore

Conclusion: a simple if statement examining the resource is inited or not is just not good enough, we need to create some sort of lock mechanism so we can ensure a single init of our resource (again the multithreaded singleton problem).

The JS lock – first try

Our goal is to create a general-purpose lock so we can reuse it. There are a few ways we can create the requested lock let’s examine the following

Our lock will be initialized with the number of users that can enter the critical section – in our case we set it to only one.

Now that we have a lock we will need to block the critical section, we will pass to the lock a callback function that will run our initialization unit.

The lock will examine how many requests are currently in the critical section if noon it will just let your callback function run, else it will queue your callback until the current user will release it. Once it has been released it the lock will fetch the first callback in the queue and run it.

Since the callback will run in a different context, be aware to make sure you bind all of your code or use an arrow function to maintain your current context (this).

class Lock {
  counter: number; // how many can enter the critical section at once
  waiters: any[];

  constructor(counter) {
    this.counter = counter; // how many users can use the resource at one, set 1 for regular lock
    this.waiters = []; // all the callback that are waiting to use the resource
  }

  hold(cb: Function) {
    if (this.counter > 0) { // there is no one waiting for the resource
      this.counter--; // update the resource is in usage
      cb();  // fire the requested callback
    } else {
      this.waiters.push(cb); // the resource is in usage you need to wait for it
    }
  }

  release() { // some one just release the resource - pop the next user who is waiting and fire it
    if (this.waiters.length > 0) { // some one released the lock - so we need to see who is waiting and fire it
      const cb = this.waiters.pop(); // get the latest request for the lock
      // select the relevant one
      // process.nextTick(cb); // if you are on node
      setTimeout(() => cb, 0); // if you are in the browser
    } else {
      this.counter++;
    }
  }
}

Ok now let’s integrate it to solve our issue, the only thing we need to remember is once we enter the lock we need to verify again that the resource is not initialized since once we entered the lock after an unknown time the resource may or may not have been initialized – so we need to validate it again (this is known as the double if in the singleton pattern example).

class Lock {
  counter: number; // how many can enter the critical section at once
  waiters: any[];

  constructor(counter) {
    this.counter = counter; // how many users can use the resource at one, set 1 for regular lock
    this.waiters = []; // all the callback that are waiting to use the resource
  }

  hold(cb: Function) {
    if (this.counter > 0) { // there is no one waiting for the resource
      this.counter--; // update the resource is in usage
      cb();  // fire the requested callback
    } else {
      this.waiters.push(cb); // the resource is in usage you need to wait for it
    }
  }

  release() { // some one just release the resource - pop the next user who is waiting and fire it
    if (this.waiters.length > 0) { // some one released the lock - so we need to see who is waiting and fire it
      const cb = this.waiters.pop(); // get the latest request for the lock
      // select the relevant one
      // process.nextTick(cb); // if you are on node
      setTimeout(() => cb, 0); // if you are in the browser
    } else {
      this.counter++;
    }
  }
}

export class Resource {
  static instance;
  static lock = new Lock(1);

  constructor(... args) {
  }

  static async getInstance() {
    if (!Singleton1.instance) {
      return new Promise((resolve, reject) => {
        Resource.lock.hold(async () => {
          if (!Resource.instance) {
            try {
              Resource.instance =
                await ResourceFactory.getInstance();
              resolve(Resource.instance);
            } catch (e) {
              reject();
            }
          }
          resolve(Resource.instance);
        });
      })

    } else {
      console.log('no need to init resource we have it')
      return Singleton1.instance;
    }

  }
}

export class ResourceFactory {
  static getInstance() {
    return new Promise((resolve, reject) => {
      console.log('init resource')
      setTimeout(() => {
        console.log('resolving resource');
        resolve('I am a resource and i am ready');
      }, 5000);
    })
  }
}

let i = 0;
const intervalId = setInterval(async () => {
  console.log('trying to init resource');
  const resource = await Singleton1.getInstance();
  console.log('resource is init');
  console.log(resource);

  if (i === 5) clearInterval(intervalId);
  i++;

}, 500);

This time the output is the following,

trying to init resource
trying to init resource
trying to init resource
trying to init resource
trying to init resource
trying to init resource
resolving resource
resource is init
I am a resource and i am ready
trying to init resource
no need to init resource we have it
resource is init
I am a resource and i am ready
trying to init resource
no need to init resource we have it
resource is init
I am a resource and i am ready
trying to init resource

We have only one resolving resource line meaning our resource was only init once.

Still, it is over complexed to use our lock since we need to wrap it each time in a promise and pass a callback function – not so user-friendly. We can do better. We can try and move the “promise” code to the lock and just await on the lock – much cleaner.

The JS Lock
The JS Lock

The JS Lock – Refactored

class Lock {
  counter: number; // how many can enter the critical section at once
  readonly waiters: {
    resolve: Function,
    reject: Function
  }[];

  constructor(counter) {
    this.counter = counter; // how many users can use the resource at one, set 1 for regular lock
    this.waiters = []; // all the callback that are waiting to use the resource
  }

  async hold(): Promise<any> {
    if (this.counter > 0) { // there is no one waiting for the resource
      this.counter--; // update the resource is in usage
      return; // let the code enter the critical section
    } else {
      const promise = new Promise((resolve, reject) => {
        this.waiters.push({resolve, reject}); // the resource is in usage you need to wait for it
      });
      return promise; // Dont call us ... will call you
    }
  }

  release() { // some one just release the resource - pop the next user who is waiting and fire it
    if (this.waiters.length > 0) { // some one released the lock - so we need to see who is waiting and fire it
      const {resolve,reject} = this.waiters.shift() // get the latest waiting request for the lock
      // process.nextTick(cb); // if you are on node
      setTimeout(() => resolve, 0); // if you are in the browser
    } else {
      this.counter++;
    }
  }
}

And now our client has transformed into

export class Resource {
  static instance;
  static lock = new Lock(1);

 constructor(... args) {}

  static async getInstance() {
    if (!Resource.instance) {
      await Resource.lock.hold();
      if (!Resource.instance) {
        Resource.instance =
          Resource.instance = await ResourceFactory.getInstance();
       Resource.lock.release() 
      }
    } else {
      console.log('no need to init resource we have it')
    }
    return Resource.instance;

  }
}

Notice we are double-checking the instance even when we are in the critical section – since once the lock is free there is a good chance that the previous holder had initialized the resource for us. Second, notice the call to the release once our resource is fully initialized. Make sure that if there is any chance that the resource initializing can throw an exception you must wrap it with try-catch and finely statement to always release the lock otherwise your code will never move to pass the lock hold request.

You are probably not initializing your resource objects correctly ( JS lock ).

Yoni Amishav


Tech lead, blogger, node js Angular and more ...


Post navigation


Leave a Reply

Free Email Updates
Get the latest content first.
We respect your privacy.
%d