From 85d40f08f2e7bfc6d7b7fae33e1a2407c61b969d Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 02 Apr 2026 19:46:30 +0000
Subject: [PATCH] Fix Windows service premature exit due to transient lock file false negatives (#623)

---
 opendj-server-legacy/src/build-tools/windows/service.c |   70 +++++++++++++++--------
 .github/workflows/deploy.yml                           |   30 +++++-----
 .github/workflows/release.yml                          |   38 ++++++------
 .github/workflows/build.yml                            |   31 +++++-----
 4 files changed, 95 insertions(+), 74 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e1809fa..c995358 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -4,7 +4,6 @@
   push:
     branches: [ 'sustaining/4.10.x','master' ]
   pull_request:
-    branches: [ 'sustaining/4.10.x','master' ]
 
 jobs:
   build-maven:
@@ -28,17 +27,17 @@
         wine --version
         version="9.4.0"; sudo wget "https://dl.winehq.org/wine/wine-mono/$version/wine-mono-$version-x86.msi" -O /tmp/wine-mono.msi
         wine msiexec /i /tmp/wine-mono.msi
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
       with:
         fetch-depth: 0
         submodules: recursive
     - name: Java ${{ matrix.Java }} (${{ matrix.os }})
-      uses: actions/setup-java@v4
+      uses: actions/setup-java@v5
       with:
         java-version: ${{ matrix.java }}
         distribution: 'zulu'
     - name: Cache Maven packages
-      uses: actions/cache@v4
+      uses: actions/cache@v5
       with:
          path: ~/.m2/repository
          key: ${{ runner.os }}-m2-repository-${{ hashFiles('**/pom.xml') }}
@@ -256,14 +255,14 @@
 
     - name: Upload Windows exe artifacts
       if: runner.os == 'Windows'
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v7
       with:
         name: windows-exe-${{ matrix.java }}
         retention-days: 5
         path: opendj-server-legacy/src/build-tools/windows/*.exe
 
     - name: Upload artifacts OpenDJ Server
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v7
       with:
         name: ${{ matrix.os }}-${{ matrix.java }}
         retention-days: 5
@@ -288,7 +287,7 @@
           - 5000:5000
     steps:
       - name: Download artifacts
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v8
         with:
           name: ubuntu-latest-11
       - name: Get latest release version
@@ -298,16 +297,16 @@
           echo "release_version=$git_version_last" >> $GITHUB_ENV
       - name: Docker meta
         id: meta
-        uses: docker/metadata-action@v5
+        uses: docker/metadata-action@v6
         with:
           images: |
             localhost:5000/${{ github.repository }}
           tags: |
             type=raw,value=${{ env.release_version }}
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v3
+        uses: docker/setup-qemu-action@v4
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
+        uses: docker/setup-buildx-action@v4
         with:
           driver-opts: network=host
       - name: Prepare Dockerfile
@@ -317,7 +316,7 @@
           cp ./opendj-server-legacy/target/package/opendj-*.zip ./opendj-packages/opendj-docker
           sed -i -E '/^#COPY opendj/s/^#//' ./opendj-packages/opendj-docker/Dockerfile
       - name: Build image (default)
-        uses: docker/build-push-action@v5
+        uses: docker/build-push-action@v7
         with:
           context: ./opendj-packages/opendj-docker
           file: ./opendj-packages/opendj-docker/Dockerfile
@@ -352,7 +351,7 @@
           - 5000:5000
     steps:
       - name: Download artifacts
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v8
         with:
           name: ubuntu-latest-11
       - name: Get latest release version
@@ -362,7 +361,7 @@
           echo "release_version=$git_version_last" >> $GITHUB_ENV
       - name: Docker meta 
         id: meta
-        uses: docker/metadata-action@v5
+        uses: docker/metadata-action@v6
         with:
           images: |
             localhost:5000/${{ github.repository }}
@@ -370,9 +369,9 @@
             type=raw,value=alpine
             type=raw,value=${{ env.release_version }}-alpine
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v3
+        uses: docker/setup-qemu-action@v4
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
+        uses: docker/setup-buildx-action@v4
         with:
           driver-opts: network=host
       - name: Prepare Dockerfile
@@ -382,7 +381,7 @@
           cp ./opendj-server-legacy/target/package/opendj-*.zip ./opendj-packages/opendj-docker
           sed -i -E '/^#COPY opendj/s/^#//' ./opendj-packages/opendj-docker/Dockerfile-alpine
       - name: Build image
-        uses: docker/build-push-action@v5
+        uses: docker/build-push-action@v7
         with:
           context: ./opendj-packages/opendj-docker
           file: ./opendj-packages/opendj-docker/Dockerfile-alpine
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index c6e0b51..8b56d45 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -27,13 +27,13 @@
           wine --version
           version="9.4.0"; sudo wget "https://dl.winehq.org/wine/wine-mono/$version/wine-mono-$version-x86.msi" -O /tmp/wine-mono.msi
           wine msiexec /i /tmp/wine-mono.msi
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         with:
           fetch-depth: 0
           submodules: recursive
           ref: ${{ github.event.workflow_run.head_branch }}
       - name: Set up Java for publishing to Maven Central Repository OSS
-        uses: actions/setup-java@v4
+        uses: actions/setup-java@v5
         with:
           java-version: ${{ github.event.workflow_run.head_branch == 'sustaining/4.10.x' && '8' || '11'}}
           distribution: 'temurin'
@@ -41,7 +41,7 @@
           server-username: MAVEN_USERNAME
           server-password: MAVEN_PASSWORD
       - name: Cache Maven packages
-        uses: actions/cache@v4
+        uses: actions/cache@v5
         with:
          path: ~/.m2/repository
          key: ${{ runner.os }}-m2-repository-${{ hashFiles('**/pom.xml') }}
@@ -71,52 +71,52 @@
         continue-on-error: true
         run: mvn javadoc:aggregate
       - name: Upload artifacts OpenDJ Server
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ Server
          path: opendj-server-legacy/target/package/*.zip
       - name: Upload artifacts OpenDJ SDK Toolkit
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ SDK Toolkit
          path: opendj-ldap-toolkit/target/*.zip
       - name: Upload artifacts OpenDJ Debian Package
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ Debian Package
          path: opendj-packages/opendj-deb/opendj-deb-standard/target/*.deb
       - name: Upload artifacts OpenDJ RPM Package
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ RPM Package
          path: opendj-packages/opendj-rpm/opendj-rpm-standard/target/rpm/opendj/RPMS/noarch/*.rpm
       - name: Upload artifacts OpenDJ MSI Package
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ MSI Package
          path: opendj-packages/opendj-msi/opendj-msi-standard/target/*.msi
       - name: Upload artifacts OpenDJ Docker Packages
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ Docker Packages
          path: opendj-packages/opendj-docker/target/Dockerfile.zip
       - name: Upload artifacts OpenDJ Openshift template
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ Openshift template
          path: opendj-packages/opendj-openshift-template/*.yaml
       - name: Upload artifacts OpenDJ Doc Generated Reference
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ Doc Generated References
          path: opendj-doc-generated-ref/target/*.zip
       - name: Upload artifacts OpenDJ DSML Gateway
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ DSML Gateway
          path: opendj-dsml-servlet/target/*.war
       - name: Upload artifacts OpenDJ Commons REST LDAP Gateway
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
          name: OpenDJ Commons REST LDAP Gateway
          path: opendj-rest2ldap-servlet/target/*.war
@@ -125,7 +125,7 @@
           git config --global user.name "Open Identity Platform Community"
           git config --global user.email "open-identity-platform-opendj@googlegroups.com"
           cd ..
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         continue-on-error: true
         with:
           repository: ${{ github.repository }}.wiki
@@ -145,7 +145,7 @@
           git commit -a -m "upload docs after deploy ${{ github.sha }}"
           git push --quiet --force
 
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         continue-on-error: true
         with:
           repository: OpenIdentityPlatform/doc.openidentityplatform.org
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9b982ac..41bb3bc 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -31,12 +31,12 @@
           wine --version
           version="9.4.0"; sudo wget "https://dl.winehq.org/wine/wine-mono/$version/wine-mono-$version-x86.msi" -O /tmp/wine-mono.msi
           wine msiexec /i /tmp/wine-mono.msi
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         with:
           fetch-depth: 0
           submodules: recursive
       - name: Set up Java for publishing to Maven Central Repository OSS
-        uses: actions/setup-java@v4
+        uses: actions/setup-java@v5
         with:
           java-version: ${{ github.event.workflow_run.head_branch == 'sustaining/4.10.x' && '8' || '11'}}
           distribution: 'temurin'
@@ -44,7 +44,7 @@
           server-username: MAVEN_USERNAME
           server-password: MAVEN_PASSWORD
       - name: Cache Maven packages
-        uses: actions/cache@v4
+        uses: actions/cache@v5
         with:
          path: ~/.m2/repository
          key: ${{ runner.os }}-m2-repository-${{ hashFiles('**/pom.xml') }}
@@ -90,7 +90,7 @@
             target/checkout/opendj-doc-generated-ref/target/*.zip
             target/checkout/opendj-dsml-servlet/target/*.war
             target/checkout/opendj-rest2ldap-servlet/target/*.war
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         continue-on-error: true
         with:
           repository: ${{ github.repository }}.wiki
@@ -113,7 +113,7 @@
            git push --quiet --force
            git push --quiet --force origin ${{ github.event.inputs.releaseVersion }}
 
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         continue-on-error: true
         with:
           repository: OpenIdentityPlatform/doc.openidentityplatform.org
@@ -136,14 +136,14 @@
     needs:
       - release-maven
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         with:
           ref: ${{ github.event.inputs.releaseVersion }}
           fetch-depth: 1
           submodules: recursive
       - name: Docker meta
         id: meta
-        uses: docker/metadata-action@v5
+        uses: docker/metadata-action@v6
         with:
           images: |
             ${{ github.repository }}
@@ -152,22 +152,22 @@
             type=raw,value=latest
             type=raw,value=${{ github.event.inputs.releaseVersion }}
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v3
+        uses: docker/setup-qemu-action@v4
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
+        uses: docker/setup-buildx-action@v4
       - name: Login to DockerHub
-        uses: docker/login-action@v3
+        uses: docker/login-action@v4
         with:
           username: ${{ secrets.DOCKER_USERNAME }}
           password: ${{ secrets.DOCKER_PASSWORD }}
       - name: Login to GHCR
-        uses: docker/login-action@v3
+        uses: docker/login-action@v4
         with:
           registry: ghcr.io
           username: ${{ github.repository_owner }}
           password: ${{ secrets.GITHUB_TOKEN }}
       - name: Build and push image
-        uses: docker/build-push-action@v5
+        uses: docker/build-push-action@v7
         continue-on-error: true
         with:
           context: ./opendj-packages/opendj-docker
@@ -184,14 +184,14 @@
     needs:
       - release-maven
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v6
         with:
           ref: ${{ github.event.inputs.releaseVersion }}
           fetch-depth: 1
           submodules: recursive
       - name: Docker meta
         id: meta
-        uses: docker/metadata-action@v5
+        uses: docker/metadata-action@v6
         with:
           images: |
             ${{ github.repository }}
@@ -200,23 +200,23 @@
             type=raw,value=alpine
             type=raw,value=${{ github.event.inputs.releaseVersion }}-alpine
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v3
+        uses: docker/setup-qemu-action@v4
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
+        uses: docker/setup-buildx-action@v4
       - name: Login to DockerHub
-        uses: docker/login-action@v3
+        uses: docker/login-action@v4
         with:
           username: ${{ secrets.DOCKER_USERNAME }}
           password: ${{ secrets.DOCKER_PASSWORD }}
       - name: Login to GHCR
-        uses: docker/login-action@v3
+        uses: docker/login-action@v4
         with:
           registry: ghcr.io
           username: ${{ github.repository_owner }}
           password: ${{ secrets.GITHUB_TOKEN }}
       - name: Build and push image
         continue-on-error: true
-        uses: docker/build-push-action@v5
+        uses: docker/build-push-action@v7
         with:
           context: ./opendj-packages/opendj-docker
           file: ./opendj-packages/opendj-docker/Dockerfile-alpine
diff --git a/opendj-server-legacy/src/build-tools/windows/service.c b/opendj-server-legacy/src/build-tools/windows/service.c
index d14eaa4..1fd7e37 100644
--- a/opendj-server-legacy/src/build-tools/windows/service.c
+++ b/opendj-server-legacy/src/build-tools/windows/service.c
@@ -715,7 +715,7 @@
     if (spawn(command, FALSE) != -1)
     {
       // Try to see if server is really stopped
-      int nTries = 10;
+      int nTries = 30;
       BOOL running = TRUE;
 
       debug("doStopApplication: the spawn of the process worked.");
@@ -1241,31 +1241,53 @@
         }
         else
         {
-      // Check current Status
-      DWORD state;
-      BOOL success = getServiceStatus(serviceName, &state);
-          if (!(success &&
-               ((state == SERVICE_STOPPED) ||
-                (state == SERVICE_STOP_PENDING))))
+          // Server appears not running - retry a few times before concluding
+          // it has actually stopped (the lock file check can be transient,
+          // e.g. during JVM GC pressure or heavy I/O after a large ldapsearch).
+          // 3 retries × 2 seconds gives up to 6 extra seconds of tolerance.
+          int retryCount = 3;
+          BOOL confirmedStopped = TRUE;
+          while (retryCount > 0)
           {
-          WORD argCount = 1;
-            const char *argc[] = {_instanceDir};
-            _serviceCurStatus = SERVICE_STOPPED;
-            debug("checking in serviceMain serviceHandler: service stopped with error.");
+            retryCount--;
+            Sleep(2000); // wait 2 seconds between retries before re-checking
+            code = isServerRunning(&running, TRUE);
+            if (code == SERVICE_RETURN_OK && running)
+            {
+              confirmedStopped = FALSE;
+              break;
+            }
+          }
 
-            updateServiceStatus (
-              _serviceCurStatus,
-              ERROR_SERVICE_SPECIFIC_ERROR,
-              -1,
-              CHECKPOINT_NO_ONGOING_OPERATION,
-              TIMEOUT_NONE,
-              _serviceStatusHandle);
-            reportLogEvent(
-              EVENTLOG_ERROR_TYPE,
-              WIN_EVENT_ID_SERVER_STOPPED_OUTSIDE_SCM,
-              argCount, argc);
-           }
-          break;
+          if (confirmedStopped)
+          {
+            // Check current Status
+            DWORD state;
+            BOOL success = getServiceStatus(serviceName, &state);
+            if (!(success &&
+                 ((state == SERVICE_STOPPED) ||
+                  (state == SERVICE_STOP_PENDING))))
+            {
+              WORD argCount = 1;
+              const char *argc[] = {_instanceDir};
+              _serviceCurStatus = SERVICE_STOPPED;
+              debug("checking in serviceMain serviceHandler: service stopped with error.");
+
+              updateServiceStatus (
+                _serviceCurStatus,
+                ERROR_SERVICE_SPECIFIC_ERROR,
+                -1,
+                CHECKPOINT_NO_ONGOING_OPERATION,
+                TIMEOUT_NONE,
+                _serviceStatusHandle);
+              reportLogEvent(
+                EVENTLOG_ERROR_TYPE,
+                WIN_EVENT_ID_SERVER_STOPPED_OUTSIDE_SCM,
+                argCount, argc);
+            }
+            break;
+          }
+          // else: server is actually still running, continue monitoring
         }
       }
     }

--
Gitblit v1.10.0