question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

@azure/msal-node-extensions error deleting lockfile in Artillery.io

See original GitHub issue

Core Library

@azure/msal-node

Core Library Version

1.0.2

Wrapper Library

Not Applicable

Wrapper Library Version

None

Description

I am using @azure/msal-node and msal-node-extensions (FilePersistenceWithDataProtection) in my Artillery.io load test script (via custom JavaScript). When I have simulated users arrive at a high rate (e.g., 100 per second), and all of them start by acquiring access tokens via acquireTokenByUsernamePassword, I get this error thrown from msal-node-extensions:

(node:4488) UnhandledPromiseRejectionWarning: PersistenceError: CrossPlatformLockError: EPERM: operation not permitted, unlink ‘(path to lockfile)’

I know that this is not a true permissions problem because when I lower the arrival rate to say, 10 per second, everything works fine. It looks to me like the FilePersistenceWithDataProtection logic for deleting the lockfile doesn’t handle the case where it is called concurrently at a high rate in a single calling process. I have attached a sample script that doesn’t require Artillery and simply uses setInterval to acquire 100 tokens per second - this reproduces the problem.

Error Message

(node:4488) UnhandledPromiseRejectionWarning: PersistenceError: CrossPlatformLockError: EPERM: operation not permitted, unlink ‘C:\RDS\intelliv8\patents\LoadTest\msal.cache.lockfile’ at Function.createCrossPlatformLockError (C:\RDS\intelliv8\patents\LoadTest\node_modules@azure\msal-node-extensions\dist\msal-node-extensions.cjs.development.js:989:12) at CrossPlatformLock._callee2$ (C:\RDS\intelliv8\patents\LoadTest\node_modules@azure\msal-node-extensions\dist\msal-node-extensions.cjs.development.js:1144:38) at tryCatch (C:\RDS\intelliv8\patents\LoadTest\node_modules@azure\msal-node-extensions\dist\msal-node-extensions.cjs.development.js:211:40) at Generator.invoke [as _invoke] (C:\RDS\intelliv8\patents\LoadTest\node_modules@azure\msal-node-extensions\dist\msal-node-extensions.cjs.development.js:441:22) at Generator.throw (C:\RDS\intelliv8\patents\LoadTest\node_modules@azure\msal-node-extensions\dist\msal-node-extensions.cjs.development.js:266:21) at asyncGeneratorStep (C:\RDS\intelliv8\patents\LoadTest\node_modules@azure\msal-node-extensions\dist\msal-node-extensions.cjs.development.js:13:24) at _throw (C:\RDS\intelliv8\patents\LoadTest\node_modules@azure\msal-node-extensions\dist\msal-node-extensions.cjs.development.js:39:9)

Msal Logs

No response

MSAL Configuration

please see the attached code snippet

Relevant Code Snippets

const fs = require("fs")
const msal = require("@azure/msal-node")
const { FilePersistenceWithDataProtection, DataProtectionScope, PersistenceCachePlugin  } = require("@azure/msal-node-extensions")

const CACHE_PATH = "./msal.cache"
const CLIENT_ID = ""
const INSTANCE = ""
const TENANT_ID = ""
const USERNAME = ""
const PASSWORD = ""

let msalConfig = null
let clientApp = null

async function init(
   cachePath,
   clientId,
   instance,
   tenantId)
{
   const windowsPersistence = await FilePersistenceWithDataProtection.create(
      cachePath,
      DataProtectionScope.CurrentUser)

   msalConfig =
   {
      auth:
      {
         clientId,
         authority: instance + tenantId,
      },
      cache:
      {
         cachePlugin: new PersistenceCachePlugin(windowsPersistence)
      }
   }

   clientApp = new msal.PublicClientApplication(msalConfig)
}

async function getAccessToken(
   username,
   password)
{
   const request =
   {
      scopes: [msalConfig.auth.clientId + "/.default"],
   }

   let response

   const accounts = await clientApp.getTokenCache().getAllAccounts()
   const account = accounts.find(acc => acc.username === username)
   if(account != null)
   {
      request.account = account
      response = await clientApp.acquireTokenSilent(request)
   }
   else
   {
      request.username = username
      request.password = password
      response = await clientApp.acquireTokenByUsernamePassword(request)
   }

   return response.accessToken
}

async function setupBeforeTesting()
{
   if(fs.existsSync(CACHE_PATH))
      fs.unlinkSync(CACHE_PATH)

   await init(
      CACHE_PATH,
      CLIENT_ID,
      INSTANCE,
      TENANT_ID)
}

async function test()
{
   await setupBeforeTesting()

   setInterval(() =>
   {
      getAccessToken(USERNAME, PASSWORD)
   },
   10)
}

test()

Reproduction Steps

I reproduced this on Windows with Node.js 14.16

  1. Edit the test script to supply CLIENT_ID and the other required constants.
  2. npm install @azure/msal-node @azure/msal-node-extensions
  3. node test.js

Expected Behavior

I expected it to handle any arbitrary concurrent call rate without failing.

Identity Provider

Azure AD / MSA

Browsers Affected (Select all that apply)

None (Server)

Regression

No response

Source

External (Customer)

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
RandScullardcommented, May 7, 2021

@samuelkubai Thanks for that clarification. Thinking about your comment, I realized that for my Artillery use case I don’t actually need a file cache for MSAL since everything is running in a single process. So I have worked around this issue by removing the cache, and it seems to work fine with large numbers of concurrent virtual users.

0reactions
sameeragcommented, Jun 3, 2021

Closing this.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Testing HTTP - Artillery.io
Ignoring certificate errors can be useful for testing in a development or staging ... Artillery will abort the request and raise an ETIMEDOUT...
Read more >
Changelog - Artillery.io
Improve handling of ThrottlingException errors when launching multiple tests ... Add delete-test-run command; describe-test-run returns extra metadata about ...
Read more >
Test Scripts - Artillery.io
An Artillery test script is a YAML file composed of two main sections: config and scenarios . The scenarios section contains definitions of...
Read more >
Using Artillery for Your Functional Testing
DELETE /logout - check that the service responds with a 204 No Content status code. Let's add these expectations and assertions to the...
Read more >
Artillery CLI
What you'll learn · How to run Artillery tests from the command line · How to run tests from a single machine with...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found