Skip to content

Commit 6f589b8

Browse files
authored
Fix extractAndParseVersionOutput time out issue and add more logging … (#614)
…during the initialization <!-- Please provide brief information about the PR, what it contains & its purpose, new behaviors after the change. And let us know here if you need any help: https://github.com/microsoft/HydraLab/issues/new --> ## Description <!-- A few words to explain your changes --> This is targeted to fix the initialization getting stack bug, the process may get stuck in: ![image](https://github.com/microsoft/HydraLab/assets/8344245/5ac28049-9351-4fb0-a472-baee10d8b7dd) ### Linked GitHub issue ID: # ## Pull Request Checklist <!-- Put an x in the boxes that apply. This is simply a reminder of what we are going to look for before merging your code. --> - [ ] Tests for the changes have been added (for bug fixes / features) - [x] Code compiles correctly with all tests are passed. - [x] I've read the [contributing guide](https://github.com/microsoft/HydraLab/blob/main/CONTRIBUTING.md#making-changes-to-the-code) and followed the recommended practices. - [ ] [Wikis](https://github.com/microsoft/HydraLab/wiki) or [README](https://github.com/microsoft/HydraLab/blob/main/README.md) have been reviewed and added / updated if needed (for bug fixes / features) ### Does this introduce a breaking change? *If this introduces a breaking change for Hydra Lab users, please describe the impact and migration path.* - [ ] Yes - [x] No ## How you tested it *Please make sure the change is tested, you can test it by adding UTs, do local test and share the screenshots, etc.* Please check the type of change your PR introduces: - [ ] Bugfix - [ ] Feature - [ ] Technical design - [ ] Build related changes - [ ] Refactoring (no functional changes, no api changes) - [ ] Code style update (formatting, renaming) or Documentation content changes - [ ] Other (please describe): ### Feature UI screenshots or Technical design diagrams *If this is a relatively large or complex change, kick it off by drawing the tech design with PlantUML and explaining why you chose the solution you did and what alternatives you considered, etc...*
1 parent 28a47a2 commit 6f589b8

4 files changed

Lines changed: 79 additions & 14 deletions

File tree

agent/src/main/java/com/microsoft/hydralab/agent/config/AppConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,27 +85,33 @@ public EnvCapabilityDiscoveryService envCapabilityDiscoveryService() throws IOEx
8585
EnvCapabilityDiscoveryService envCapabilityDiscoveryService = new EnvCapabilityDiscoveryService();
8686
envCapabilityDiscoveryService.setEnableScan(true);
8787
envCapabilityDiscoveryService.discover();
88+
logger.info("envCapabilityDiscoveryService discover is completed");
8889
return envCapabilityDiscoveryService;
8990
}
9091

9192
@Bean
9293
public AgentManagementService agentManagementService(StorageServiceClientProxy storageServiceClientProxy,
9394
DeviceStatusListenerManager deviceStatusListenerManager,
9495
EnvCapabilityDiscoveryService envCapabilityDiscoveryService) {
96+
logger.info("start agentManagementService instantiation");
9597
AgentManagementService agentManagementService = new AgentManagementService();
9698
File testBaseDir = new File(appOptions.getTestCaseResultLocation());
9799
if (!testBaseDir.exists()) {
98100
if (!testBaseDir.mkdirs()) {
99101
throw new RuntimeException("agentManager dir.mkdirs() failed: " + testBaseDir);
100102
}
101103
}
104+
logger.info("testBaseDir is created in {}", testBaseDir.getAbsolutePath());
105+
102106
agentManagementService.setTestBaseDir(testBaseDir);
103107
File preAppDir = new File(appOptions.getPreAppStorageLocation());
104108
if (!preAppDir.exists()) {
105109
if (!preAppDir.mkdirs()) {
106110
throw new RuntimeException("agentManager dir.mkdirs() failed: " + preAppDir);
107111
}
108112
}
113+
logger.info("preAppDir is created in {}", preAppDir.getAbsolutePath());
114+
109115
agentManagementService.setPreAppDir(preAppDir);
110116
agentManagementService.setPreInstallFailurePolicy(
111117
shutdownIfFail ? Const.PreInstallFailurePolicy.SHUTDOWN : Const.PreInstallFailurePolicy.IGNORE);
@@ -127,6 +133,7 @@ public AgentManagementService agentManagementService(StorageServiceClientProxy s
127133
agentManagementService.setEnvInfo(envCapabilityDiscoveryService.getEnvInfo());
128134
agentManagementService.setRegistryServer(registryServer);
129135

136+
logger.info("done with agentManagementService instantiation");
130137
return agentManagementService;
131138
}
132139

agent/src/main/java/com/microsoft/hydralab/agent/environment/EnvCapabilityDiscoveryService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.microsoft.hydralab.common.entity.agent.EnvCapability;
44
import com.microsoft.hydralab.common.entity.agent.EnvInfo;
5+
import lombok.Getter;
56
import org.slf4j.Logger;
67

78
import java.io.IOException;
@@ -11,7 +12,9 @@
1112

1213
public class EnvCapabilityDiscoveryService {
1314
Logger logger = org.slf4j.LoggerFactory.getLogger(EnvCapabilityDiscoveryService.class);
15+
@Getter
1416
private final EnvInfo envInfo = new EnvInfo();
17+
@Getter
1518
private EnvCapabilityScanner scanner;
1619

1720
private boolean enableScan = false;
@@ -61,7 +64,4 @@ private void determineEnvironmentComponents(String osName) {
6164
}
6265
}
6366

64-
public EnvInfo getEnvInfo() {
65-
return envInfo;
66-
}
6767
}

agent/src/main/java/com/microsoft/hydralab/agent/environment/EnvCapabilityScanner.java

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,38 @@
11
package com.microsoft.hydralab.agent.environment;
22

3+
import com.google.common.annotations.VisibleForTesting;
34
import com.microsoft.hydralab.common.entity.agent.EnvCapability;
4-
import org.apache.commons.io.IOUtils;
55
import org.apache.commons.lang3.StringUtils;
66
import org.jetbrains.annotations.NotNull;
77
import org.slf4j.Logger;
88

99
import java.io.BufferedReader;
1010
import java.io.File;
1111
import java.io.IOException;
12+
import java.io.InputStream;
1213
import java.io.InputStreamReader;
1314
import java.nio.charset.StandardCharsets;
1415
import java.util.ArrayList;
1516
import java.util.Arrays;
1617
import java.util.HashMap;
1718
import java.util.List;
1819
import java.util.Map;
20+
import java.util.concurrent.Callable;
21+
import java.util.concurrent.ExecutionException;
22+
import java.util.concurrent.ExecutorService;
23+
import java.util.concurrent.Executors;
24+
import java.util.concurrent.FutureTask;
1925
import java.util.concurrent.TimeUnit;
26+
import java.util.concurrent.TimeoutException;
2027
import java.util.regex.Matcher;
2128
import java.util.regex.Pattern;
2229

2330
public abstract class EnvCapabilityScanner {
24-
Logger logger = org.slf4j.LoggerFactory.getLogger(EnvCapabilityScanner.class);
31+
static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(EnvCapabilityScanner.class);
2532
protected Map<String, String> systemEnv = System.getenv();
2633
static Pattern versionPattern = Pattern.compile("([0-9]+\\.[0-9]+\\.[0-9]+)");
34+
static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
35+
private static final long MAX_WAIT_TIME_SECONDS_GET_VERSION = 15;
2736

2837
@SuppressWarnings("checkstyle:InterfaceIsType")
2938
public interface VariableNames {
@@ -41,10 +50,12 @@ public List<EnvCapability> scan() throws IOException {
4150
if (!systemEnv.containsKey(scanVariable)) {
4251
continue;
4352
}
44-
logger.info("Scan system variable {} with value {}", scanVariable, systemEnv.get(scanVariable));
53+
LOGGER.info("Scan system variable {} with value {}", scanVariable, systemEnv.get(scanVariable));
4554
}
4655

56+
LOGGER.info("start scanning capabilities");
4757
ArrayList<File> files = scanPathExecutables(getPathVariableName());
58+
LOGGER.info("Completed scanning capabilities, {}", files);
4859
List<EnvCapability> capabilities = createCapabilities(files);
4960
scanCapabilityVersion(capabilities);
5061
return capabilities;
@@ -69,33 +80,77 @@ private void scanCapabilityVersion(List<EnvCapability> capabilities) throws IOEx
6980
}
7081
}
7182

72-
private void extractAndParseVersionOutput(EnvCapability capability) throws IOException {
83+
@VisibleForTesting
84+
void extractAndParseVersionOutput(EnvCapability capability) throws IOException {
85+
LOGGER.info("Will extractAndParseVersionOutput for {}, {}, {}", capability, capability.getFile().getAbsolutePath(), capability.getKeyword().getFetchVersionParam());
7386
Process process = Runtime.getRuntime().exec(new String[]{capability.getFile().getAbsolutePath(), capability.getKeyword().getFetchVersionParam()});
74-
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
75-
BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
87+
try (InputStream stdStream = process.getInputStream();
88+
InputStream errorStream = process.getErrorStream()) {
7689
// combine this in case that some output is provided through stdout and some through stderr
90+
String stdIO = null;
91+
try {
92+
stdIO = readInputStreamWithTimeout(stdStream, MAX_WAIT_TIME_SECONDS_GET_VERSION, TimeUnit.SECONDS);
93+
} catch (ExecutionException e) {
94+
LOGGER.warn("extractAndParseVersionOutput Exception when getting stdIO of " + capability.getKeyword().name(), e);
95+
}
96+
String stdError = null;
97+
try {
98+
stdError = readInputStreamWithTimeout(errorStream, MAX_WAIT_TIME_SECONDS_GET_VERSION, TimeUnit.SECONDS);
99+
} catch (ExecutionException e) {
100+
LOGGER.warn("extractAndParseVersionOutput Exception when getting stdError of " + capability.getKeyword().name());
101+
}
102+
77103
String versionOutput = String.format("Standard Output: %s\nError Output: %s",
78-
StringUtils.trim(IOUtils.toString(reader)),
79-
StringUtils.trim(IOUtils.toString(error)));
104+
StringUtils.trim(stdIO),
105+
StringUtils.trim(stdError));
106+
107+
LOGGER.info("extractAndParseVersionOutput versionOutput: {}", versionOutput);
108+
80109
boolean exited = process.waitFor(5, TimeUnit.SECONDS);
81110
if (!exited) {
82-
logger.warn("Failed to get version of " + capability.getKeyword().name());
111+
LOGGER.warn("Failed to get version of " + capability.getKeyword().name());
83112
}
84113
capability.getKeyword().setVersionOutput(versionOutput);
85114

86115
Matcher matcher = versionPattern.matcher(versionOutput);
87116
if (matcher.find()) {
88117
capability.setVersion(matcher.group());
89118
} else {
90-
logger.warn("Failed to get version of " + capability.getKeyword().name() + " in " + versionOutput);
119+
LOGGER.warn("Failed to get version of " + capability.getKeyword().name() + " in " + versionOutput);
91120
}
92121
} catch (InterruptedException e) {
93-
logger.error("Failed to get version of " + capability.getKeyword().name() + " at " + capability.getFile().getAbsolutePath(), e);
122+
LOGGER.error("Failed to get version of " + capability.getKeyword().name() + " at " + capability.getFile().getAbsolutePath(), e);
94123
} finally {
95124
process.destroy();
96125
}
97126
}
98127

128+
static String readInputStreamWithTimeout(InputStream is, long timeout, TimeUnit unit) throws ExecutionException, InterruptedException {
129+
StringBuilder result = new StringBuilder();
130+
Callable<String> readTask = () -> {
131+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
132+
String line;
133+
while ((line = reader.readLine()) != null) {
134+
result.append(line).append("\n");
135+
}
136+
return result.toString();
137+
}
138+
};
139+
140+
FutureTask<String> futureTask = new FutureTask<>(readTask);
141+
EXECUTOR_SERVICE.execute(futureTask);
142+
143+
try {
144+
return futureTask.get(timeout, unit);
145+
} catch (TimeoutException e) {
146+
String resultString = result.toString();
147+
LOGGER.warn("TimeoutException when getting resultString: " + resultString, e);
148+
return resultString;
149+
} finally {
150+
futureTask.cancel(true);
151+
}
152+
}
153+
99154
protected abstract String getPathVariableName();
100155

101156
public List<File> listExecutableFiles(String path) {
@@ -104,6 +159,7 @@ public List<File> listExecutableFiles(String path) {
104159
if (listOfFiles == null) {
105160
return null;
106161
}
162+
LOGGER.info("Scanning path {} with a count of {}", path, listOfFiles.length);
107163
ArrayList<File> files = new ArrayList<>();
108164
for (File file : listOfFiles) {
109165
if (isExecutable(file)) {
@@ -134,6 +190,7 @@ protected ArrayList<File> scanPathExecutables(String pathVarName) {
134190
}
135191
String[] paths = path.split(getPathVariableSeparator());
136192
// System.out.println(JSON.toJSONString(Arrays.asList(paths)));
193+
LOGGER.info("Scanning paths with a count of {}: {}", paths.length, Arrays.asList(paths));
137194
ArrayList<File> files = new ArrayList<>();
138195
for (String p : paths) {
139196
List<File> executableFiles = listExecutableFiles(p);

agent/src/test/java/com/microsoft/hydralab/agent/environment/EnvCapabilityDiscoveryServiceTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,5 @@ public void testFunctionAvailability() {
6666
envCapabilityRequirements);
6767
Assertions.assertFalse(agentManagementService.getFunctionAvailabilities().get(1).isAvailable());
6868
}
69+
6970
}

0 commit comments

Comments
 (0)