Repository: electron/electron (language: cpp)

Issue:
[Bug]: `app.requestSingleInstanceLock()` returns false on first start
### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a bug report that matches the one I want to file, without success.

### Electron Version

17.3.1, also affected: 16, 17, 18, 19, 20

### What operating system are you using?

macOS

### Operating System Version

12.3, also affected: Win, Linux

### What arch are you using?

x64

### Last Known Working Electron version

15.3.4

### Expected Behavior

Requesting `app.requestSingleInstanceLock()` on first start of the app should return `true`.

### Actual Behavior

Requesting `app.requestSingleInstanceLock()` on first start of the app returns `false`.

### Testcase Gist URL

https://gist.github.com/6d51100568b78f98c5177cb27d370f9f

### Additional Information

It seems that the issue only exists when setting the `userData` path to a non-existent folder (which is most likely the case when you start the app for the first time on a fresh computer). Subsequent starts result in correct behavior because the folder has been created by then. The issue was not present in E15 and below; I'll bisect further.

The issue becomes problematic when implementing a behavior as described [in the docs](https://www.electronjs.org/docs/latest/api/app#apprequestsingleinstancelockadditionaldata) because the app will just quit on first start:
```js
const gotTheLock = app.requestSingleInstanceLock(additionalData)
if (!gotTheLock) {
  app.quit()
} else {...
```

cc @miniak 

_funfact: this was a pain to debug since it crashed our CI in a very obscure way 😆_ 


Discussion / hints:
Caused by https://github.com/electron/electron/pull/30594. Most likely missed lines in constructor in patch:
```cpp
  // The user_data_dir may have not been created yet.
  base::ThreadRestrictions::ScopedAllowIO allow_io;
  base::CreateDirectoryAndGetError(user_data_dir, nullptr);
```

Fix patch:
diff --git a/shell/browser/api/electron_api_app.cc b/shell/browser/api/electron_api_app.cc
index 73db1e756f885..e3a88a28b0872 100644
--- a/shell/browser/api/electron_api_app.cc
+++ b/shell/browser/api/electron_api_app.cc
@@ -1148,6 +1148,8 @@ bool App::RequestSingleInstanceLock(gin::Arguments* args) {
 
   base::FilePath user_dir;
   base::PathService::Get(chrome::DIR_USER_DATA, &user_dir);
+  // The user_dir may not have been created yet.
+  base::CreateDirectoryAndGetError(user_dir, nullptr);
 
   auto cb = base::BindRepeating(&App::OnSecondInstance, base::Unretained(this));
   auto wrapped_cb = base::BindRepeating(NotificationCallbackWrapper, cb);
diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts
index 718223229572b..0389b70931a07 100644
--- a/spec-main/api-app-spec.ts
+++ b/spec-main/api-app-spec.ts
@@ -228,6 +228,13 @@ describe('app module', () => {
       expect(code1).to.equal(0);
     });
 
+    it('returns true when setting non-existent user data folder', async function () {
+      const appPath = path.join(fixturesPath, 'api', 'singleton-userdata');
+      const instance = cp.spawn(process.execPath, [appPath]);
+      const [code] = await emittedOnce(instance, 'exit');
+      expect(code).to.equal(0);
+    });
+
     async function testArgumentPassing (testArgs: SingleInstanceLockTestArgs) {
       const appPath = path.join(fixturesPath, 'api', 'singleton-data');
       const first = cp.spawn(process.execPath, [appPath, ...testArgs.args]);
diff --git a/spec/fixtures/api/singleton-userdata/main.js b/spec/fixtures/api/singleton-userdata/main.js
new file mode 100644
index 0000000000000..98f6841b4282a
--- /dev/null
+++ b/spec/fixtures/api/singleton-userdata/main.js
@@ -0,0 +1,12 @@
+const { app } = require('electron');
+const fs = require('fs');
+const path = require('path');
+
+// non-existent user data folder should not break requestSingleInstanceLock()
+// ref: https://github.com/electron/electron/issues/33547
+const userDataFolder = path.join(app.getPath('home'), 'electron-test-singleton-userdata');
+fs.rmSync(userDataFolder, { force: true, recursive: true });
+app.setPath('userData', userDataFolder);
+
+const gotTheLock = app.requestSingleInstanceLock();
+app.exit(gotTheLock ? 0 : 1);
diff --git a/spec/fixtures/api/singleton-userdata/package.json b/spec/fixtures/api/singleton-userdata/package.json
new file mode 100644
index 0000000000000..1269c0a67d5dd
--- /dev/null
+++ b/spec/fixtures/api/singleton-userdata/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "electron-test-singleton-userdata",
+  "main": "main.js"
+}


Test patch: