Job 类是一个抽象类,其中定义了五种状态,其中 PENDING 状态基本是在你一次点击了很多 ParaGraph 时才会出现的情况,这时有一些任务虽然被提交但是依然没有在运行,五种状态详细信息如下:
Job 类所有子类如下:
Job 类中最关键的就是 run 方法(注意这个类不是线程类,仅仅是有一个名为 run 的方法而已),当调度类调度到一个 job 时,会调用其 run 方法( FIFO 调度是这样,但是 RemoteScheduler 和 ParallelScheduler 由于对 job 又一次进行封装,因此调用的是封装类的 run 方法,封装类的 run 方法才会调用 Job 的 run 方法),run 方法关键代码如下,其中
+ JobProgressPoller 是一个线程,作用就是每隔 progressUpdateIntervalMs 回调 NotebookServer 类中的更新前台 Paragraph 运行时间的方法
+ jobRun 方法是 job 的子类应该实现的具体运行各种 job 的方法
JobProgressPoller progressUpdator = null;
try {
progressUpdator = new JobProgressPoller(this, progressUpdateIntervalMs);
progressUpdator.start();
dateStarted = new Date();
setResult(jobRun());
this.exception = null;
errorMessage = null;
dateFinished = new Date();
progressUpdator.terminate();
}
Paragraph 类身兼两职,一个是作为一个实体对象记录着网页中的一个 Paragraph 的相关信息,比如 title、text、user、dateUpdated、config、settings、results、result 等属性。
另一个职责就是作为 Job 的子类,重写了 jobRun 方法。关键流程为:
1. jobRun 方法首先根据 replName 和其他信息获取负责本次任务执行的 LazyOpenInterpreter 对象,然后调用其 interpret 方法。
2. 之后的 context.out 相关操作其实没有什么用,因此目前没有运行在主进程中的解释器,因此这个 out 在主进程中其实是没有值的,解释器 JVM 中的 RemoteInterpreterServer 类在返回之前已经将流的结果和普通结果合并,因此其实 interpret 方法返回的 ret 中已经包含全部结果。
3. 之后将这个 res 返回给调用该方法的方法,即父类中的 run 方法。
@Override
protected Object jobRun() throws Throwable {
String replName = getRequiredReplName();
Interpreter repl = getRepl(replName);
// 省略
try {
InterpreterContext context = getInterpreterContext();
InterpreterContext.set(context);
InterpreterResult ret = repl.interpret(script, context);
if (Code.KEEP_PREVIOUS_RESULT == ret.code()) {
return getReturn();
}
context.out.flush();
List resultMessages = context.out.toInterpreterResultMessage();
resultMessages.addAll(ret.message());
InterpreterResult res = new InterpreterResult(ret.code(), resultMessages);
Paragraph p = getUserParagraph(getUser());
if (null != p) {
p.setResult(res);
p.settings.setParams(settings.getParams());
}
return res;
} finally {
InterpreterContext.remove();
}
}
这个类的 jobRun 方法其实和 Paragraph 类的 jobRun 方法差不多,这里不再赘述。除此之外,还有一些属性是为调度类运行该 job 服务的,其中 interpreter 运行实际是 LazyOpenInterpreter 类,只不过里面包装的不再是解释器代理对象,而是 SparkInterpreter 之类的解释器对象,script 就是要运行的脚本,context 就是每次运行一个 ParaGraph 的上下文,results 就是运行一个 ParaGraph 的结果。
private Interpreter interpreter;
private String script;
private InterpreterContext context;
private Map infos;
private Object results;
JobListener 是一个接口,接口中只定义了三个方法,这三个方法其实是更新 Job 的状态的时候使用的:
public void onProgressUpdate(Job job, int progress);
public void beforeStatusChange(Job job, Job.Status before, Job.Status after);
public void afterStatusChange(Job job, Job.Status before, Job.Status after);
其所有子类如下,其中除了 InterpretJobListener , 其余所有类都运行在主进程中:
由于该类只有一个实现类( Test 类不算),是 NotebookServer 类,且只有一个方法是 getParagraphJobListener 方法,因此实际运行时调用的就是下面的 NotebookServer 类的方法,返回的类型就是 ParagraphListenerImpl 类:
@Override
public ParagraphJobListener getParagraphJobListener(Note note) {
return new ParagraphListenerImpl(this, note);
}
可以看到这个接口三个方法都是更新网页中的 ParaGarph 的时候使用的,但是目前更新网页的方法并不是回调这个接口中的这三个方法而是使用 RemoteInterpreterEventPoller 类(流方式)或是 ParagraphListenerImpl 类中的 afterStatusChange (返回结果方式)进行更新前台的一个 ParaGraph,这个接口存在的意义可能是为了以后增加运行在主进程中的解释器用的吧。
这个类在 NotebookServer 类中被定义,当外界调用该类实际就是调用了 NotebookServer 类本身的一些更新前台的方法(而且之前说过 NotebookServer 类的对象在主进程中且只有一个)。由于它相当于实现了两个接口,因此其实有双重任务。
这三个方法是作用是更新前台 Job 的状态和 ParaGraph 的显示,比如其中的 afterStatusChange 方法:
@Override
public void afterStatusChange(Job job, Status before, Status after) {
// 省略
if (job instanceof Paragraph) {
Paragraph p = (Paragraph) job;
p.setStatusToUserParagraph(job.getStatus());
notebookServer.broadcastParagraph(note, p);
}
try {
notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
} catch (IOException e) {
LOG.error("can not broadcast for job manager {}", e);
}
}
上面说过目前还没有发现 ParagraphJobListener 接口中的三个方法有什么作用,但是这里简单说明一下调用顺序,其中 onOutputAppend 在这个类中的实现是这样的(注意 NotebookServer 类中还有两个与之同名的方法,分别负责将流追加到前台以及 helium 中内容追加到前台):
@Override
public void onOutputAppend(Paragraph paragraph, int idx, String output) {
Message msg =
new Message(OP.PARAGRAPH_APPEND_OUTPUT).put("noteId", paragraph.getNote().getId())
.put("paragraphId", paragraph.getId()).put("data", output);
notebookServer.broadcast(paragraph.getNote().getId(), msg);
}
上面的方法又在 Note 类中被回调,方法如下,上面说过 jobListenerFactory.getParagraphJobListener(this) 返回的实现类是 ParagraphListenerImpl:
@Override
public void onOutputAppend(Paragraph paragraph, int idx, String output) {
if (jobListenerFactory != null) {
ParagraphJobListener listener = jobListenerFactory.getParagraphJobListener(this);
if (listener != null) {
listener.onOutputAppend(paragraph, idx, output);
}
}
}
而 Note 类中的这个方法又在 ParaGraph 类中的一个匿名内部类中被回调,其中该方法中 getListener 方法返回的其实就是该 ParaGraph 类所在的 Note 的对象,因为 Note 对象本身也是一个 ParagraphJobListener:
@Override
public void onAppend(int index, InterpreterResultMessageOutput out, byte[] line) {
((ParagraphJobListener) getListener()).onOutputAppend(self, index, new String(line));
}
Note 类中实现的 ParagraphJobListener 类中的方法全部都通过调用 ParagraphListenerImpl 类相应的方法完成功能,但目前常用功能,都是直接通过 ParagraphListenerImpl 类来完成功能的,暂时还没发现这六个方法有什么作用。
这个类是一个线程类,在 RemoteScheduler 类中,供 RemoteScheduler 类使用,就是对 Job 类的封装,实现了 Runnable, JobListener 接口,使得调度类可以并发执行 job,只有四个属性,其中 scheduler 是 RemoteScheduler 对象,job 是该线程所对应的 Job 类的对象,jobExecuted 标识该 job 是否已经执行完( Job 类的 run 方法不是异步执行的 ),jobSubmittedRemotely 方法标识该线程中的 job 是否已经提交到远程解释器:
private Scheduler scheduler;
private Job job;
private boolean jobExecuted;
boolean jobSubmittedRemotely;
其中 run 方法关键代码如下:
JobStatusPoller jobStatusPoller = new JobStatusPoller(1500, 100, 500,
job, this);
jobStatusPoller.start();
if (listener != null) {
listener.jobStarted(scheduler, job);
}
job.run();
jobExecuted = true;
jobSubmittedRemotely = true;
jobStatusPoller.shutdown();
其中 JobStatusPoller 类的作用是每隔指定时间去解释器 JVM 中取一下当前 Job 的进度,根据取到的状态来调用自身的 listener 的相应方法,从 JobRunner 类的 run 方法可以看出其实这个 listener 就是 JobRunner 线程本身,这也是为什么 JobRunner 类需要实现 JobListener 接口,而下面的 ParallelScheduler.JobRunner 类就不需要实现 JobListener 接口,就是因为当 job 的状态发生改变时 JobStatusPoller 类需要根据 Job 的状态来调用 JobRunner 类的 afterStatusChange 方法, afterStatusChange 方法再调用 job 的 setStatus 方法,setStatus 方法进而再调用 ParagraphListenerImpl 类的 afterStatusChange 方法最终调用 NotebookServer 去更新前台。关键字段如下:
private boolean terminate;
private JobListener listener;
private Job job;
Status lastStatus;
jobStatusPoller 线程启动之后,调用 RemoteScheduler 类的 listener (实际运行类为 SchedulerFactory 类)的 jobStarted 方法,然后调用 job 的 run 方法, job 的 run 方法执行完成之后代表这个 job 已经运行完毕,之后就是处理 job 运行之后的事情。
这个类也是一个线程类,在 ParallelScheduler 类中,供 ParallelScheduler 类使用,代码基本类似,这里不再赘述。
成之后代表这个 job 已经运行完毕,之后就是处理 job 运行之后的事情。
InterpretJobListener 类在 RemoteInterpreterServer 中被定义,由于解释器 JVM 进程在 job 运行状态改变时不需要进行操作( 是主进程一直在 poll ),所以该类其实并没有实现什么有用的方法。