Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public class BundleWatcherImpl implements SynchronousBundleListener, ServiceList

private List<ServerInfo> serverInfos = new ArrayList<>();

// Lock object to synchronize startup message display
private final Object startupMessageLock = new Object();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startupMessageAlreadyDisplayed should be declared volatile here, because it is read outside the synchronized block (line 290) as part of a double-checked locking pattern. Without volatile, the Java Memory Model allows a thread to read a stale cached value indefinitely — meaning a thread could keep seeing false even after another thread has set it to true inside the lock. In practice, stale reads are rare on strongly-ordered architectures like x86 (which is why the bug may not show up during development), but they are much more frequent on weakly-ordered architectures like ARM — commonly used in cloud/container deployments. Adding volatile guarantees that every write is immediately visible to all threads, which is what makes the outer check reliable.

References:


public void setRequiredBundles(Map<String, Boolean> requiredBundles) {
this.requiredBundles = new ConcurrentHashMap<>(requiredBundles);
}
Expand Down Expand Up @@ -274,43 +277,43 @@ private void destroyScheduler() {
}

private void checkStartupComplete() {
if (!isStartupComplete()) {
startScheduler(getBundleCheckTask());
return;
}
if (scheduledFuture != null) {
destroyScheduler();
}
if (!allAdditionalBundleStarted()) {
if (!isStartupComplete()) {
startScheduler(getBundleCheckTask());
return;
} else if (!allAdditionalBundleStarted()) {
startScheduler(getAdditionalBundleCheckTask());
return;
}
if (scheduledFuture != null) {
destroyScheduler();
}
if (!startupMessageAlreadyDisplayed) {
long totalStartupTime = System.currentTimeMillis() - startupTime;
synchronized (startupMessageLock) {
if (!startupMessageAlreadyDisplayed) {
long totalStartupTime = System.currentTimeMillis() - startupTime;

List<String> logoLines = serverInfos.get(serverInfos.size() - 1).getLogoLines();
if (logoLines != null && !logoLines.isEmpty()) {
logoLines.forEach(System.out::println);
List<String> logoLines = serverInfos.get(serverInfos.size() - 1).getLogoLines();
if (logoLines != null && !logoLines.isEmpty()) {
logoLines.forEach(System.out::println);
}
System.out.println("--------------------------------------------------------------------------------------------");
serverInfos.forEach(serverInfo -> {
String versionMessage = MessageFormat.format(" {0} {1} ({2,date,yyyy-MM-dd HH:mm:ssZ} // {3} // {4} // {5}) ",
StringUtils.rightPad(serverInfo.getServerIdentifier(), 12, " "), serverInfo.getServerVersion(),
serverInfo.getServerBuildDate(), serverInfo.getServerTimestamp(), serverInfo.getServerScmBranch(),
serverInfo.getServerBuildNumber());
System.out.println(versionMessage);
LOGGER.info(versionMessage);
});
System.out.println("--------------------------------------------------------------------------------------------");
System.out.println("Server successfully started " + requiredBundles.size() + " bundles and " + requiredServicesFilters.size()
+ " required " + "services in " + totalStartupTime + " ms");
LOGGER.info("Server successfully started {} bundles and {} required services in {} ms", requiredBundles.size(),
requiredServicesFilters.size(), totalStartupTime);
startupMessageAlreadyDisplayed = true;
shutdownMessageAlreadyDisplayed = false;
}
}
System.out.println("--------------------------------------------------------------------------------------------");
serverInfos.forEach(serverInfo -> {
String versionMessage = MessageFormat.format(" {0} {1} ({2,date,yyyy-MM-dd HH:mm:ssZ} // {3} // {4} // {5}) ",
StringUtils.rightPad(serverInfo.getServerIdentifier(), 12, " "), serverInfo.getServerVersion(),
serverInfo.getServerBuildDate(), serverInfo.getServerTimestamp(), serverInfo.getServerScmBranch(),
serverInfo.getServerBuildNumber());
System.out.println(versionMessage);
LOGGER.info(versionMessage);
});
System.out.println("--------------------------------------------------------------------------------------------");
System.out.println("Server successfully started " + requiredBundles.size() + " bundles and " + requiredServicesFilters.size()
+ " required " + "services in " + totalStartupTime + " ms");
LOGGER.info("Server successfully started {} bundles and {} required services in {} ms", requiredBundles.size(),
requiredServicesFilters.size(), totalStartupTime);
startupMessageAlreadyDisplayed = true;
shutdownMessageAlreadyDisplayed = false;
}
}

Expand Down
Loading