提交 b4daf282 编写于 作者: K kohsuke

implemented double-launch detection (#1236)


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@6926 71c3de6d-444a-0410-be80-ed276b4c234a
上级 3023d4b5
......@@ -2,6 +2,7 @@ package hudson.triggers;
import hudson.DependencyRunner;
import hudson.ExtensionPoint;
import hudson.util.DoubleLaunchChecker;
import hudson.DependencyRunner.ProjectRunnable;
import hudson.model.AbstractProject;
import hudson.model.Action;
......@@ -203,6 +204,8 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
public static void init() {
timer.scheduleAtFixedRate(new Cron(), 1000*60, 1000*60/*every minute*/);
new DoubleLaunchChecker().schedule();
// clean up fingerprint once a day
long HOUR = 1000*60*60;
long DAY = HOUR*24;
......
......@@ -7,11 +7,11 @@ import com.thoughtworks.xstream.converters.collections.AbstractCollectionConvert
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.*;
import hudson.model.Descriptor.FormException;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import org.apache.maven.model.Dependency;
import java.io.IOException;
import java.util.ArrayList;
......@@ -111,6 +111,18 @@ public class DescribableList<T extends Describable<T>, D extends Descriptor<T>>
data.replaceBy(newList);
}
/**
* Picks up {@link DependecyDeclarer}s and allow it to build dependencies.
*/
public void buildDependencyGraph(AbstractProject owner,DependencyGraph graph) {
for (Object o : this) {
if (o instanceof DependecyDeclarer) {
DependecyDeclarer dd = (DependecyDeclarer) o;
dd.buildDependencyGraph(owner,graph);
}
}
}
public interface Owner {
/**
* Called whenever the list is changed, so that it can be saved.
......@@ -120,6 +132,8 @@ public class DescribableList<T extends Describable<T>, D extends Descriptor<T>>
/**
* {@link Converter} implementation for XStream.
*
* Serializaion form is compatible with plain {@link List}.
*/
public static final class ConverterImpl extends AbstractCollectionConverter {
CopyOnWriteList.ConverterImpl copyOnWriteListConverter;
......
package hudson.util;
import hudson.model.Hudson;
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
import org.apache.commons.io.FileUtils;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.logging.Logger;
/**
* Makes sure that no other Hudson uses our <tt>HUDSON_HOME</tt> directory,
* to forestall the problem of running multiple instances of Hudson that point to the same data directory.
*
* <p>
* This set up error occasionally happens especialy when the user is trying to reassign the context path of the app,
* and it results in a hard-to-diagnose error, so we actively check this.
*
* <p>
* The mechanism is simple. This class occasionally updates a known file inside the hudson home directory,
* and whenever it does so, it monitors the timestamp of the file to make sure no one else is updating
* this file. In this way, while we cannot detect the problem right away, within a reasonable time frame
* we can detect the collision.
*
* <p>
* More traditional way of doing this is to use a lock file with PID in it, but unfortunately in Java,
* there's no reliabe way to obtain PID.
*
* @author Kohsuke Kawaguchi
* @since 1.178
*/
public class DoubleLaunchChecker {
/**
* The timestamp of the owner file when we updated it for the last time.
* 0 to indicate that there was no update before.
*/
private long lastWriteTime = 0L;
/**
* Once the error is reported, the user can choose to ignore and proceed anyway,
* in which case the flag is set to true.
*/
private boolean ignore = false;
private final Random random = new Random();
public final File home;
public DoubleLaunchChecker() {
home = Hudson.getInstance().getRootDir();
}
protected void execute() {
File timestampFile = new File(home,".owner");
long t = timestampFile.lastModified();
if(t!=0 && lastWriteTime!=0 && t!=lastWriteTime && !ignore) {
// we noticed that someone else have updated this file.
// switch GUI to display this error.
Hudson.getInstance().servletContext.setAttribute("app",this);
LOGGER.severe("Collision detected. timestamp="+t+", expected="+lastWriteTime);
// we need to continue updating this file, so that the other Hudson would notice the problem, too.
}
try {
FileUtils.writeStringToFile(timestampFile,"This file is used to make sure only one Hudson instance uses this directory");
lastWriteTime = timestampFile.lastModified();
} catch (IOException e) {
// if failed to write, err on the safe side and assume things are OK.
lastWriteTime=0;
}
schedule();
}
/**
* Schedules the next execution.
*/
public void schedule() {
// randomize the scheduling so that multiple Hudson instances will write at the file at different time
int MINUTE = 1000*60;
Trigger.timer.schedule(new SafeTimerTask() {
protected void doRun() {
execute();
}
},(random.nextInt(30*MINUTE)+60*MINUTE));
}
/**
* Serve all URLs with the index view.
*/
public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
rsp.setStatus(SC_INTERNAL_SERVER_ERROR);
req.getView(this,"index.jelly").forward(req,rsp);
}
/**
* Ignore the problem and go back to using Hudson.
*/
public void doIgnore(StaplerRequest req, StaplerResponse rsp) throws IOException {
ignore = true;
Hudson.getInstance().servletContext.setAttribute("app",Hudson.getInstance());
rsp.sendRedirect2(req.getContextPath()+'/');
}
private static final Logger LOGGER = Logger.getLogger(DoubleLaunchChecker.class.getName());
}
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<l:layout>
<l:header title="Hudson">
<!--meta http-equiv="refresh" content="5" /-->
</l:header>
<l:side-panel />
<l:main-panel>
<h1>${%Error}</h1>
<p>
${%message(it.home)}
</p>
<div>
<form action="${rootURL}/ignore">
<f:submit value="Ignore this problem and keep using Hudson anyway" />
</form>
</div>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
message=\
Hudson detected that you appear to be running more than one \
instance of Hudson that share the same home directory ''{0}''. \
This greatly confuses Hudson and you will likely experience \
strange behaviors, so please correct the situation.
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册