Skip to content

Commit

Permalink
[FIXED JENKINS-22248]
Browse files Browse the repository at this point in the history
Using unique ID for each run to allow multiple executions to run on the same directory
  • Loading branch information
kohsuke committed May 29, 2014
1 parent f5d75f9 commit 6655d25
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 42 deletions.
Expand Up @@ -37,8 +37,6 @@
*/
public final class BourneShellScript extends FileMonitoringTask {

private static final String BOURNE_SCRIPT_FILE = ".jenkins-script.sh";

private final String script;

@DataBoundConstructor public BourneShellScript(String script) {
Expand All @@ -49,24 +47,33 @@ public String getScript() {
return script;
}

@Override protected FileMonitoringController doLaunch(FilePath workspace, Launcher launcher, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException {
if (!launcher.isUnix()) {
throw new IOException("Bourne shell scripts can only be run on Unix nodes");
}
workspace.child(BOURNE_SCRIPT_FILE).write(script, "UTF-8");
StringBuilder shell = new StringBuilder("sh '").append(workspace).append('/').append(BOURNE_SCRIPT_FILE).append("' >'").append(workspace).append('/').append(LOG_FILE).append("' 2>&1; ");
shell.append("echo $? >'").append(workspace).append('/').append(RESULT_FILE).append('\'');
launcher.launch().cmds("nohup", "sh", "-c", shell.toString()).envs(envVars).pwd(workspace).start();
return new ShellController();
@Override protected FileMonitoringController doLaunch(FilePath ws, Launcher launcher, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException {
// KK: Commenting out --- Hey, don't forget about Cygwin!
// if (!launcher.isUnix()) {
// throw new IOException("Bourne shell scripts can only be run on Unix nodes");
// }
ShellController c = new ShellController(ws);

c.getScriptFile(ws).write(script, "UTF-8");

String cmd = String.format("sh '%s' > '%s' 2>&1; echo $? > '%s'",
c.getScriptFile(ws),
c.getLogFile(ws),
c.getResultFile(ws)
);

launcher.launch().cmds("nohup", "sh", "-c", cmd).envs(envVars).pwd(ws).start();
return c;
}

private static final class ShellController extends FileMonitoringController {

@Override public void cleanup(FilePath workspace) throws IOException, InterruptedException {
super.cleanup(workspace);
workspace.child(BOURNE_SCRIPT_FILE).delete();
private ShellController(FilePath ws) throws IOException, InterruptedException {
super(ws);
}

public FilePath getScriptFile(FilePath ws) {
return controlDir(ws).child("script.sh");
}
}

@Extension public static final class DescriptorImpl extends DurableTaskDescriptor {
Expand Down
Expand Up @@ -35,6 +35,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -45,25 +46,18 @@ public abstract class FileMonitoringTask extends DurableTask {

private static final Logger LOGGER = Logger.getLogger(FileMonitoringTask.class.getName());

/** Workspace-relative filename for {@link #doLaunch}. */
protected static final String LOG_FILE = ".jenkins-log.txt";

/** Workspace-relative filename for {@link #doLaunch}. */
protected static final String RESULT_FILE = ".jenkins-result.txt";

private static String id(FilePath workspace) {
return Util.getDigestOf(workspace.getRemote());
}

@Override public final Controller launch(EnvVars env, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
workspace.child(LOG_FILE).delete();
workspace.child(RESULT_FILE).delete();
env.put("JENKINS_SERVER_COOKIE", "durable-" + id(workspace)); // ensure getCharacteristicEnvVars does not match, so Launcher.killAll will leave it alone
return doLaunch(workspace, launcher, listener, env);
}

/**
* Should start a process which sends output to {@link #LOG_FILE} in the workspace and finally writes its exit code to {@link #RESULT_FILE}.
* Should start a process which sends output to {@linkplain FileMonitoringController#getLogFile(FilePath) log file}
* in the workspace and finally writes its exit code to {@linkplain FileMonitoringController#getResultFile(FilePath) result file}.
* @param workspace the workspace to use
* @param launcher a way to launch processes
* @param listener build console log
Expand All @@ -73,11 +67,23 @@ private static String id(FilePath workspace) {
protected abstract FileMonitoringController doLaunch(FilePath workspace, Launcher launcher, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException;

protected static class FileMonitoringController extends Controller {

/**
* Unique ID among all the {@link Controller}s that share the same workspace.
*/
private final String id = Util.getDigestOf(UUID.randomUUID().toString()).substring(0,8);

This comment has been minimized.

Copy link
@jglick

jglick Jun 2, 2014

Member

Seems a clumsy way of formatting a random integer as hex.

This comment has been minimized.

Copy link
@kohsuke

kohsuke Jun 2, 2014

Author Member

Feel free to change it.


/**
* Byte offset in the file that has been reported thus far.
*/
private long lastLocation;

public FileMonitoringController(FilePath ws) throws IOException, InterruptedException {
// can't keep ws reference because Controller is expected to be serializable
controlDir(ws).mkdirs();
}

@Override public final boolean writeLog(FilePath workspace, OutputStream sink) throws IOException, InterruptedException {
FilePath log = workspace.child(LOG_FILE);
FilePath log = getLogFile(workspace);
long len = log.length();
if (len > lastLocation) {
// TODO more efficient to use RandomAccessFile or similar in a FileCallable:
Expand All @@ -96,7 +102,7 @@ protected static class FileMonitoringController extends Controller {
}

@Override public final Integer exitStatus(FilePath workspace) throws IOException, InterruptedException {
FilePath status = workspace.child(RESULT_FILE);
FilePath status = getResultFile(workspace);
if (status.exists()) {
return Integer.parseInt(status.readToString().trim());
} else {
Expand All @@ -109,10 +115,29 @@ protected static class FileMonitoringController extends Controller {
}

@Override public void cleanup(FilePath workspace) throws IOException, InterruptedException {
workspace.child(LOG_FILE).delete();
workspace.child(RESULT_FILE).delete();
controlDir(workspace).deleteRecursive();
}

/**
* Directory in which this controller can place files.
*/
public FilePath controlDir(FilePath ws) {
return ws.child("."+id);
}

/**
* File in which the exit code of the process should be reported.
*/
public FilePath getResultFile(FilePath workspace) {
return controlDir(workspace).child("jenkins-result.txt");
}

/**
* File in which the stdout/stderr
*/
public FilePath getLogFile(FilePath workspace) {
return controlDir(workspace).child("jenkins-log.txt");
}
}

}
Expand Up @@ -36,10 +36,6 @@
* Runs a Windows batch script.
*/
public final class WindowsBatchScript extends FileMonitoringTask {

private static final String BATCH_SCRIPT_FILE_1 = ".jenkins-wrap.bat";
private static final String BATCH_SCRIPT_FILE_2 = ".jenkins-main.bat";

private final String script;

@DataBoundConstructor public WindowsBatchScript(String script) {
Expand All @@ -50,24 +46,35 @@ public String getScript() {
return script;
}

@Override protected FileMonitoringController doLaunch(FilePath workspace, Launcher launcher, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException {
@Override protected FileMonitoringController doLaunch(FilePath ws, Launcher launcher, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException {
if (launcher.isUnix()) {
throw new IOException("Batch scripts can only be run on Windows nodes");
}
workspace.child(BATCH_SCRIPT_FILE_1).write("call " + BATCH_SCRIPT_FILE_2 + " >" + LOG_FILE + " 2>&1\r\necho %ERRORLEVEL% >" + RESULT_FILE + "\r\n", "UTF-8");
workspace.child(BATCH_SCRIPT_FILE_2).write(script, "UTF-8");
launcher.launch().cmds("cmd", "/c", workspace.child(BATCH_SCRIPT_FILE_1).getRemote()).envs(envVars).pwd(workspace).start();
return new BatchController();
BatchController c = new BatchController(ws);

c.getBatchFile1(ws).write(String.format("call %s > %s 2>&1\r\necho %%ERRORLEVEL%% > %s\r\n",
c.getBatchFile2(ws),
c.getLogFile(ws),
c.getResultFile(ws)
), "UTF-8");
c.getBatchFile2(ws).write(script, "UTF-8");

launcher.launch().cmds("cmd", "/c", c.getBatchFile1(ws).getRemote()).envs(envVars).pwd(ws).start();
return c;
}

private static final class BatchController extends FileMonitoringController {
private BatchController(FilePath ws) throws IOException, InterruptedException {
super(ws);
}

@Override public void cleanup(FilePath workspace) throws IOException, InterruptedException {
super.cleanup(workspace);
workspace.child(BATCH_SCRIPT_FILE_1).delete();
workspace.child(BATCH_SCRIPT_FILE_2).delete();
public FilePath getBatchFile1(FilePath ws) {
return controlDir(ws).child("jenkins-wrap.bat");
}

public FilePath getBatchFile2(FilePath ws) {
return controlDir(ws).child("jenkins-main.bat");
}
}

@Extension public static final class DescriptorImpl extends DurableTaskDescriptor {
Expand Down

0 comments on commit 6655d25

Please sign in to comment.