1

I am working on a partner manager and some code need to be atomic because currently there is race condition and cant work when 2 clients calls same resource at same time. retrievePartners method returns partners and that method should me atomic. Basicaly partners are the limited resources and providing mechanism should deal only one client (asking for partner) at a time.

I have been told the code below works for atomic operation, since javascript is atomic by native.

let processingQueue = Promise.resolve();

function doStuffExclusively() {

  processingQueue = processingQueue.then(() => {
      return fetch('http://localhost', {method: 'PUT', body: ...});
  }).catch(function(e){
      throw e;
  });

  return processingQueue;
}

doStuffExclusively()
doStuffExclusively()
doStuffExclusively()

However this code is basic, my code has some await that calls another await , and so on. I want to apply that mechanism for below code but really dont know how to do, I tried few tings but no work. Can not get await work inside a then statement.

I am also confused is above code returns true in then part of processingQueue. However in my case, I return an array, or throw an error message. Should I return something to get it work as above.

Here is the function I want to make atomic just like the above code. I tried to put everything in this function in then section, before return statement, but did not worked, since

export class Workout {
  constructor (config) {
    this.instructorPeer = new jet.Peer(config)
    this.instructorPeer.connect()
  }

  async createSession (partnerInfo) {
    const partners = { chrome: [], firefox: [], safari: [], ie: [] }
    const appropriatePartners = await this.retrievePartners(partnerInfo)
    Object.keys(appropriatePartners).forEach(key => {
      appropriatePartners[key].forEach(partner => {
        const newPartner = new Partner(this.instructorPeer, partner.id)
        partners[key].push(newPartner)
      })
    })

    return new Session(partners)
  }
  async retrievePartners (capabilities) {
    const appropriatePartners = { chrome: [], firefox: [], safari: [], ie: [] }
    const partners = await this.getAllPartners()

    // first check if there is available appropriate Partners
    Object.keys(capabilities.type).forEach(key => {
      let typeNumber = parseInt(capabilities.type[key])
      for (let i = 0; i < typeNumber; i++) {
        partners.forEach((partner, i) => {
          if (
            key === partner.value.type &&
            partner.value.isAvailable &&
            appropriatePartners[key].length < typeNumber
          ) {
            appropriatePartners[key].push(partner)
            console.log(appropriatePartners[key].length)
          }
        })

        if (appropriatePartners[key].length < typeNumber) {
          throw new Error(
            'Sorry there are no appropriate Partners for this session'
          )
        }
      }
    })

    Object.keys(appropriatePartners).forEach(key => {
      appropriatePartners[key].forEach(partner => {
        this.instructorPeer.set('/partners/' + partner.id + '/states/', {
          isAvailable: false
        })
      })
    })

    return appropriatePartners
  }

  async getAllPartners (capabilities) {
    const partners = []
    const paths = await this.instructorPeer.get({
      path: { startsWith: '/partners/' }
    })
    paths.forEach((path, i) => {
      if (path.fetchOnly) {
        let obj = {}
        obj.value = path.value
        obj.id = path.path.split('/partners/')[1]
        obj.value.isAvailable = paths[i + 1].value.isAvailable
        partners.push(obj)
      }
    })
    return partners
  }

Here is the code that calls it

async function startTest () {
  const capabilities = {
    type: {
      chrome: 1
    }
  }
  const workoutServerConfig = {
    url: 'ws://localhost:8090'
  }
  const workout = createWorkout(workoutServerConfig)
  const session = await workout.createSession(capabilities)
  const session1 = await workout.createSession(capabilities)

and here is what I tried so for and not worked, session is not defined et all

let processingQueue = Promise.resolve()

    export class Workout {
  constructor (config) {
    this.instructorPeer = new jet.Peer(config)
    this.instructorPeer.connect()
this.processingQueue = Promise.resolve()
  }

  async createSession (partnerInfo) {
    this.processingQueue = this.processingQueue.then(() => {
      const partners = { chrome: [], firefox: [], safari: [], ie: [] }
      const appropriatePartners = this.retrievePartners(partnerInfo)
      Object.keys(appropriatePartners).forEach(key => {
        appropriatePartners[key].forEach(partner => {
          const newPartner = new Partner(this.instructorPeer, partner.id)
          partners[key].push(newPartner)
        })
      })

      return new Session(partners)
    })
  }
Rasim Avcı
  • 1,123
  • 2
  • 10
  • 16
  • Is this server side of client side Javascript? I'm guessing this is some kind of node service? – Liam Apr 17 '18 at 08:49
  • Possible duplicate of [How to implement a lock in JavaScript](https://stackoverflow.com/questions/5346694/how-to-implement-a-lock-in-javascript) – Liam Apr 17 '18 at 08:50
  • `const partners = await this.getAllPartners()` I think should be `const partners = return this.getAllPartners()` .. as it s within an async ? – Pogrindis Apr 17 '18 at 08:50
  • getAllPartners returns all partners in the system, available or not. Other post may be similar, but here I am asking how can I implement lock code for my code ? – Rasim Avcı Apr 17 '18 at 10:51
  • Sidenote: the catch() call defined that way is basically the same as if it there wasn't catch at all. – Frax Apr 17 '18 at 10:54
  • sorry const partners = await this.getAllPartners() is wrong written, it is actually const partners = this.getAllPartners() – Rasim Avcı Apr 17 '18 at 10:58
  • Frax, which catch you mention about, can be more specific ? – Rasim Avcı Apr 17 '18 at 11:08
  • when I use processingQueue.then, I cant use await or return inside it. – Rasim Avcı Apr 17 '18 at 11:27
  • so as I understand, atomic code should include async code but it cant include any await statement. ? – Rasim Avcı Apr 17 '18 at 11:32
  • This is something I recommend solving server side. What are you using for your DB? What language is your server written in? – Potato Apr 17 '18 at 11:44
  • What you are going to need to do is provide a lock on the resource. That needs to be done on the server, or your DB. Client side, do not know if it is possible. They are separate processes which know nothing about each other. – Potato Apr 17 '18 at 11:59
  • There is no server, a bit of express code with node-jet, which holds partners status on the network, partners are resources which are defined on a browser. – Rasim Avcı Apr 22 '18 at 20:46

1 Answers1

1

This is promise-based locking, based on the facts that:

1) the .then() handler will only be called once the lock has resolved.

2) once the .then() handler begins executing, no other JS code will execute, due to JS' execution model.

The overall structure of the approach you cited is correct.

The main issue I see with your code is that const appropriatePartners = this.retrievePartners(partnerInfo) will evaluate to a promise, because retrievePartners is async. You want to:

const appropriatePartners = await this.retrievePartners(partnerInfo).

This will cause your lock's executor to block on the retrievePartners call, whereas currently you are simply grabbing a promise wrapping that call's eventual return value.

Edit: See jsfiddle for an example.

In sum:

1) make the arrow function handling lock resolution async

2) make sure it awaits the return value of this.retrievePartners, otherwise you will be operating on the Promise, not the resolved value.

jagthebeetle
  • 705
  • 6
  • 11
  • Thanks jagthebeetle for detailed answer, and I am stil confused, what you advce me to do ? – Rasim Avcı Apr 17 '18 at 13:26
  • To begin with, I think that adding await to your retrievePartners call is necessary. Side note, but this only implements locking within a single JS runtime (so the server has to do this on behalf of clients, if I understand your setup). – jagthebeetle Apr 20 '18 at 11:16
  • I see you also write method getAllPartners, actualy its a method just rings all partners registered to node-jet. I added code for getAllPartners in original post. Can you rewrite your fiddle code according to that ? – Rasim Avcı Apr 23 '18 at 16:00
  • Are you sure this will work ? I want to access createSession same time with different tests in same spec file whom they uses same workout object. My old tests can not run in paralle bco they try to get same resource (partner) at same time. Meanhile I get Workout is not a constructor for your code.. I see your constructor has not accept paramter and also you write new Map instead new.Jet which brokes whole thing.. – Rasim Avcı Apr 23 '18 at 16:04
  • And I see you implemented locked = true and locked = false inside getAllPartners instead of retrievePartners. The method retrievePartners should be atomic – Rasim Avcı Apr 23 '18 at 16:26