Skip to content

Commit 98764be

Browse files
committed
Merge branch '7.12'
2 parents 775223a + f278c47 commit 98764be

5 files changed

Lines changed: 136 additions & 50 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,16 @@ DO NOT ADD CHANGES HERE - ADD THEM USING log_change.sh
1313
~~~
1414

1515

16-
## [v7.12.4] - 2026-05-14
16+
* Bug : Fix typo in query snippet name (`Eval first first value` => `Eval first value`).
17+
18+
* Bug **#5549** : Make jffi extract its native library to the same dir as the LMDB native library file. Add the config prop `providedJffiLibraryPath` to allow for a provided jffi lib.
1719

1820
* Refactor : Improve logging and system info output for Data Feed Keys.
1921

2022
* Feature **#5526** : Add event logging to ask stroom AI.
2123

2224
* Bug **#5544** : Fix DocRef bug.
2325

24-
25-
## [v7.12.3] - 2026-05-11
26-
2726
* Feature **#5183** : Add title to dashboard link function.
2827

2928
* Bug **#5518** : Fix expression editor bug.
@@ -36,21 +35,12 @@ DO NOT ADD CHANGES HERE - ADD THEM USING log_change.sh
3635

3736
* Bug : Fix permission exceptions when getting volume system info.
3837

39-
40-
## [v7.12.2] - 2026-05-05
41-
4238
* Bug **#5532** : Relax Data Feed Identities validation so salt is optional.
4339

44-
45-
## [v7.12.1] - 2026-04-27
46-
4740
* Bug **#5520** : Fix annotation decoration in queries/dashboards not working if the EventId/StreamId columns are not longs.
4841

4942
* Feature : Add config prop `stroom.annotation.eventLinkCacheSizeLimit` (default 1,000,000) to protect Stroom from caching too many annotation to event links. If this limit is exceeded, the query will error.
5043

51-
52-
## [v7.12.0] - 2026-04-27
53-
5444
* Feature : Change the annotation caching to invalidate on a field basis rather than the whole annotation.
5545

5646
* Bug : Fix annotation dash/query columns not updating when status/label/collection names are deleted/modified.

stroom-config/stroom-config-app/src/test/resources/stroom/config/app/expected.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@ appConfig:
588588
lifecycle:
589589
enabled: true
590590
lmdbLibrary:
591+
providedJffiLibraryPath: null
591592
providedSystemLibraryPath: null
592593
systemLibraryExtractDir: "lmdb_library"
593594
logging:

stroom-core-client-widget/src/main/java/edu/ycp/cs/dh/acegwt/public/ace/snippets/stroom_query.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ eval EventId = first(EventId)
3636
},
3737
{
3838
"tabTrigger": "first",
39-
"name": "Eval first first value",
39+
"name": "Eval first value",
4040
"content": "eval ${1:field_name} = first(${1})\n$0",
4141
},
4242
];

stroom-lmdb/src/main/java/stroom/lmdb/LmdbLibrary.java

Lines changed: 91 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import stroom.util.logging.LambdaLogger;
2222
import stroom.util.logging.LambdaLoggerFactory;
2323
import stroom.util.logging.LogUtil;
24+
import stroom.util.shared.NullSafe;
2425

2526
import jakarta.inject.Inject;
2627
import jakarta.inject.Provider;
@@ -31,7 +32,6 @@
3132
import java.nio.file.Files;
3233
import java.nio.file.Path;
3334
import java.util.Objects;
34-
import java.util.Optional;
3535

3636
@Singleton // The LMDB lib is dealt with statically by LMDB java so only want to initialise it once
3737
public class LmdbLibrary {
@@ -67,49 +67,107 @@ private synchronized void configureLibraryUnderLock(final LmdbLibraryConfig lmdb
6767
if (!HAS_LIBRARY_BEEN_CONFIGURED) {
6868
LOGGER.info("Configuring LMDB system library");
6969

70-
final Path lmdbSystemLibraryPath = Optional.ofNullable(lmdbLibraryConfig.getProvidedSystemLibraryPath())
71-
.map(pathCreator::toAppPath)
72-
.orElse(null);
70+
LOGGER.debug(() -> LogUtil.message("""
71+
configureLibraryUnderLock() - lmdbLibraryConfig: {},
72+
{},
73+
{},
74+
{},
75+
{}""",
76+
lmdbLibraryConfig,
77+
dumpSystemProp(LmdbLibraryConfig.LMDB_EXTRACT_DIR_PROP),
78+
dumpSystemProp(LmdbLibraryConfig.LMDB_NATIVE_LIB_PROP),
79+
dumpSystemProp(LmdbLibraryConfig.JFFI_EXTRACT_DIR_PROP),
80+
dumpSystemProp(LmdbLibraryConfig.JFFI_NATIVE_LIB_PROP)));
81+
82+
final Path lmdbSystemLibraryPath = NullSafe.get(
83+
lmdbLibraryConfig.getProvidedSystemLibraryPath(),
84+
pathCreator::toAppPath);
85+
final Path jffiNativeLibraryPath = NullSafe.get(
86+
lmdbLibraryConfig.getProvidedJffiLibraryPath(),
87+
pathCreator::toAppPath);
88+
89+
boolean extractLmdb = false;
90+
boolean extractJffi = false;
7391

7492
if (lmdbSystemLibraryPath != null) {
7593
if (!Files.isReadable(lmdbSystemLibraryPath)) {
76-
throw new RuntimeException("Unable to read LMDB system library at " +
77-
lmdbSystemLibraryPath.toAbsolutePath().normalize());
94+
throw new RuntimeException("Unable to read LMDB system library at " + lmdbSystemLibraryPath);
7895
}
7996
// jakarta.validation should ensure the path is valid if set
8097
final String lmdbNativeLibProp = LmdbLibraryConfig.LMDB_NATIVE_LIB_PROP;
81-
System.setProperty(lmdbNativeLibProp, lmdbSystemLibraryPath.toAbsolutePath().normalize().toString());
82-
LOGGER.info("Using provided LMDB system library file. Setting prop {} to '{}'",
98+
System.setProperty(lmdbNativeLibProp, lmdbSystemLibraryPath.toString());
99+
LOGGER.info("Using provided LMDB native library file. Setting prop {} to '{}'",
83100
lmdbNativeLibProp, lmdbSystemLibraryPath);
84101
} else {
85-
final Path systemLibraryExtractDir = getLibraryExtractDir(lmdbLibraryConfig);
102+
extractLmdb = true;
103+
}
104+
105+
final String jffiNativeLibProp = LmdbLibraryConfig.JFFI_NATIVE_LIB_PROP;
106+
final String jffiPropVal = System.getenv(jffiNativeLibProp);
107+
if (NullSafe.isNonBlankString(jffiPropVal)) {
108+
LOGGER.info("Using provided JFFI native library file. Property {} already set to '{}'",
109+
jffiNativeLibProp, jffiPropVal);
110+
} else if (jffiNativeLibraryPath != null) {
111+
if (!Files.isReadable(jffiNativeLibraryPath)) {
112+
throw new RuntimeException("Unable to read JFFI native library at " + jffiNativeLibraryPath);
113+
}
114+
// jakarta.validation should ensure the path is valid if set
115+
System.setProperty(jffiNativeLibProp, jffiNativeLibraryPath.toString());
116+
LOGGER.info("Using provided JFFI native library file. Setting prop {} to '{}'",
117+
jffiNativeLibProp, lmdbSystemLibraryPath);
118+
} else {
119+
extractJffi = true;
120+
}
86121

87-
// LMDB extracts the lib on boot to a unique temp file and should delete on JVM exit,
122+
LOGGER.debug("configureLibraryUnderLock() - extractLmdb: {}, extractJffi: {}", extractLmdb, extractJffi);
123+
if (extractLmdb || extractJffi) {
124+
final Path systemLibraryExtractDir = getLibraryExtractDir(lmdbLibraryConfig);
125+
// LMDB/JFFI extract the lib on boot to a unique temp file and should delete on JVM exit,
88126
// but just in case clear out any old ones.
89127
cleanUpExtractDir(systemLibraryExtractDir);
90128

129+
// Extract them both to the same place as both will need a non 'noexec' mount.
130+
91131
// Set the location to extract the bundled LMDB binary to
92-
final String lmdbExtractDirProp = LmdbLibraryConfig.LMDB_EXTRACT_DIR_PROP;
93-
System.setProperty(
94-
lmdbExtractDirProp,
95-
systemLibraryExtractDir.toAbsolutePath().normalize().toString());
96-
LOGGER.info("Bundled LMDB system library binary will be extracted. Setting prop {} to '{}'",
97-
lmdbExtractDirProp, systemLibraryExtractDir);
98-
HAS_LIBRARY_BEEN_CONFIGURED = true;
132+
if (extractLmdb) {
133+
final String lmdbExtractDirProp = LmdbLibraryConfig.LMDB_EXTRACT_DIR_PROP;
134+
System.setProperty(lmdbExtractDirProp, systemLibraryExtractDir.toString());
135+
LOGGER.info("Bundled LMDB native library binary will be extracted and used. " +
136+
"Setting system prop '{}' to '{}'",
137+
lmdbExtractDirProp, systemLibraryExtractDir);
138+
}
139+
140+
// Set the location to extract the bundled Jffi binary to
141+
if (extractJffi) {
142+
final String jffiExtractDirProp = LmdbLibraryConfig.JFFI_EXTRACT_DIR_PROP;
143+
if (System.getProperty(jffiExtractDirProp) == null) {
144+
// Allow a jvm arg to override our config
145+
System.setProperty(jffiExtractDirProp, systemLibraryExtractDir.toString());
146+
LOGGER.info("Bundled JFFI native library binary will be extracted and used. " +
147+
"Setting system prop '{}' to '{}'",
148+
jffiExtractDirProp, systemLibraryExtractDir);
149+
}
150+
}
99151
}
152+
HAS_LIBRARY_BEEN_CONFIGURED = true;
100153
} else {
101154
LOGGER.debug("Another thread beat us to it");
102155
}
103156
}
104157

158+
private String dumpSystemProp(final String propName) {
159+
return "'" + propName + "': '" + System.getProperty(propName) + "'";
160+
}
161+
105162
private Path getLibraryExtractDir(final LmdbLibraryConfig lmdbLibraryConfig) {
106163
final String extractDirStr = lmdbLibraryConfig.getSystemLibraryExtractDir();
107164

108165
final String extractDirPropName = lmdbLibraryConfig.getFullPathStr(LmdbLibraryConfig.EXTRACT_DIR_PROP_NAME);
109166

110167
Path extractDir;
111168
if (extractDirStr == null) {
112-
LOGGER.warn("LMDB system library extract dir is not set ({}), falling back to temporary directory {}.",
169+
LOGGER.warn("LMDB system library extract dir is not set ({}), falling back to temporary directory {}. " +
170+
"If the temporary directory is mounted with 'noexec' any use of LMDB will error.",
113171
extractDirPropName,
114172
tempDirProvider.get());
115173

@@ -119,6 +177,7 @@ private Path getLibraryExtractDir(final LmdbLibraryConfig lmdbLibraryConfig) {
119177
} else {
120178
extractDir = pathCreator.toAppPath(extractDirStr);
121179
}
180+
extractDir = extractDir.toAbsolutePath().normalize();
122181

123182
try {
124183
if (LOGGER.isDebugEnabled()) {
@@ -152,21 +211,27 @@ private Path getLibraryExtractDir(final LmdbLibraryConfig lmdbLibraryConfig) {
152211
}
153212

154213
private void cleanUpExtractDir(final Path extractDir) {
155-
try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(
156-
extractDir, "lmdbjava-native-library-*.so")) {
214+
cleanUpExtractDir(extractDir, "lmdbjava-native-library-*.so", "LMDB native library");
215+
// The jffi file should not really ever be there because it is deleted once loaded, but just in case
216+
// See com.kenai.jffi.internal.StubLoader
217+
cleanUpExtractDir(extractDir, "jffi*.so", "JFFI native library");
218+
}
157219

158-
for (final Path redundantLibraryFilePath : directoryStream) {
159-
LOGGER.info("Deleting redundant LMDB library file "
160-
+ redundantLibraryFilePath.toAbsolutePath().normalize());
220+
private static void cleanUpExtractDir(final Path extractDir, final String glob, final String name) {
221+
try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(extractDir, glob)) {
222+
for (final Path file : directoryStream) {
223+
LOGGER.info("Deleting redundant {} file {}", name, file.toAbsolutePath().normalize());
161224
try {
162-
Files.deleteIfExists(redundantLibraryFilePath);
225+
Files.deleteIfExists(file);
163226
} catch (final IOException e) {
164-
LOGGER.error("Unable to delete file " + extractDir.toAbsolutePath().normalize(), e);
227+
LOGGER.error(() -> LogUtil.message("Unable to delete {} file {}",
228+
name, file.toAbsolutePath().normalize()), e);
165229
// swallow error as these old files don't matter really
166230
}
167231
}
168232
} catch (final IOException e) {
169-
throw new RuntimeException("Error listing contents of " + extractDir.toAbsolutePath().normalize());
233+
throw new RuntimeException(LogUtil.message("Error listing contents of {} with glob '{}'",
234+
extractDir.toAbsolutePath().normalize(), glob));
170235
}
171236
}
172237
}

stroom-lmdb/src/main/java/stroom/lmdb/LmdbLibraryConfig.java

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,59 +32,89 @@
3232
public class LmdbLibraryConfig extends AbstractConfig implements IsStroomConfig {
3333

3434
static final String SYSTEM_LIBRARY_PATH_PROP_NAME = "providedSystemLibraryPath";
35+
static final String JFFI_LIBRARY_PATH_PROP_NAME = "providedJffiLibraryPath";
3536
static final String EXTRACT_DIR_PROP_NAME = "systemLibraryExtractDir";
3637
static final String DEFAULT_LIBRARY_EXTRACT_SUB_DIR_NAME = "lmdb_library";
3738

3839
// These are dups of org.lmdbjava.Library.LMDB_* but that class is pkg private for some reason.
3940
public static final String LMDB_EXTRACT_DIR_PROP = "lmdbjava.extract.dir";
4041
public static final String LMDB_NATIVE_LIB_PROP = "lmdbjava.native.lib";
42+
public static final String JFFI_EXTRACT_DIR_PROP = "jffi.extract.dir";
43+
public static final String JFFI_NATIVE_LIB_PROP = "jffi.boot.library.path";
4144

4245
private final String providedSystemLibraryPath;
46+
private final String providedJffiLibraryPath;
4347
private final String systemLibraryExtractDir;
4448

4549
public LmdbLibraryConfig() {
4650
providedSystemLibraryPath = null;
51+
providedJffiLibraryPath = null;
4752
systemLibraryExtractDir = DEFAULT_LIBRARY_EXTRACT_SUB_DIR_NAME;
4853
}
4954

5055
@SuppressWarnings("unused")
5156
@JsonCreator
5257
public LmdbLibraryConfig(@JsonProperty(SYSTEM_LIBRARY_PATH_PROP_NAME) final String providedSystemLibraryPath,
58+
@JsonProperty(JFFI_LIBRARY_PATH_PROP_NAME) final String providedJffiLibraryPath,
5359
@JsonProperty(EXTRACT_DIR_PROP_NAME) final String systemLibraryExtractDir) {
5460
this.providedSystemLibraryPath = providedSystemLibraryPath;
61+
this.providedJffiLibraryPath = providedJffiLibraryPath;
5562
this.systemLibraryExtractDir = systemLibraryExtractDir;
5663
}
5764

5865
@ValidFilePath
5966
@RequiresRestart(RestartScope.SYSTEM)
6067
@JsonProperty(SYSTEM_LIBRARY_PATH_PROP_NAME)
61-
@JsonPropertyDescription("The path to a provided LMDB system library file. If unset the LMDB binary " +
68+
@JsonPropertyDescription(
69+
"The path to a provided LMDB native library file. If unset the LMDB binary " +
6270
"bundled with Stroom will be extracted to 'systemLibraryExtractDir'. This property can be used if " +
6371
"you already have LMDB installed or want to make use of a package manager provided instance. " +
64-
"If you set this property care needs to be taken over version compatibility between the version " +
65-
"of LMDBJava (that Stroom uses to interact with LMDB) and the version of the LMDB binary.")
72+
"If you set this property care needs to be taken over version compatibility between the version " +
73+
"of LMDBJava (that Stroom uses to interact with LMDB) and the version of the LMDB binary. " +
74+
"By default this is unset.")
6675
public String getProvidedSystemLibraryPath() {
6776
return providedSystemLibraryPath;
6877
}
6978

79+
@ValidFilePath
80+
@RequiresRestart(RestartScope.SYSTEM)
81+
@JsonProperty(JFFI_LIBRARY_PATH_PROP_NAME)
82+
@JsonPropertyDescription(
83+
"The path to a provided JFFI native library file, which is used by LMDBJava. If unset the JFFI binary " +
84+
"bundled with Stroom will be extracted to 'systemLibraryExtractDir'. This property can be used if " +
85+
"you provide your own version of JFFI. " +
86+
"If you set this property care needs to be taken over version compatibility between the version " +
87+
"of JFFI and the version of the JFFI binary. " +
88+
"By default this is unset.")
89+
public String getProvidedJffiLibraryPath() {
90+
return providedJffiLibraryPath;
91+
}
92+
7093
@RequiresRestart(RestartScope.SYSTEM)
7194
@JsonProperty(EXTRACT_DIR_PROP_NAME)
72-
@JsonPropertyDescription("The directory to extract the bundled LMDB system library to. Only used if " +
73-
"property providedSystemLibraryPath is not set. On boot Stroom will extract the LMDB binary to this " +
74-
"location. It will also delete old copies of the LMDB system library if found.")
95+
@JsonPropertyDescription(
96+
"The directory to extract the bundled LMDB and JFFI native libraries to. Only used if " +
97+
"property providedSystemLibraryPath or property providedJffiLibraryPath are not set. " +
98+
"On boot Stroom will clear this directory and then extract the native libraries into it. " +
99+
"IMPORTANT: This directory must not have the 'noexec' mount option, or all LMDB use will error. " +
100+
"Also, the contents are ephemeral, so should ideally be mounted using tmpfs or similar.")
75101
public String getSystemLibraryExtractDir() {
76102
return systemLibraryExtractDir;
77103
}
78104

79105
@Override
80106
public String toString() {
81107
return "LmdbLibraryConfig{" +
82-
"providedSystemLibraryPath='" + providedSystemLibraryPath + '\'' +
83-
", systemLibraryExtractDir='" + systemLibraryExtractDir + '\'' +
84-
'}';
108+
"providedSystemLibraryPath='" + providedSystemLibraryPath + '\'' +
109+
", providedJffiLibraryPath='" + providedJffiLibraryPath + '\'' +
110+
", systemLibraryExtractDir='" + systemLibraryExtractDir + '\'' +
111+
'}';
85112
}
86113

87114
public LmdbLibraryConfig withSystemLibraryExtractDir(final String systemLibraryExtractDir) {
88-
return new LmdbLibraryConfig(providedSystemLibraryPath, systemLibraryExtractDir);
115+
return new LmdbLibraryConfig(
116+
providedSystemLibraryPath,
117+
providedJffiLibraryPath,
118+
systemLibraryExtractDir);
89119
}
90120
}

0 commit comments

Comments
 (0)