1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.background;
16
17 import java.lang.ref.WeakReference;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.concurrent.atomic.AtomicBoolean;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.htmlunit.WebClient;
25 import org.htmlunit.WebWindow;
26
27
28
29
30
31
32
33
34 public class DefaultJavaScriptExecutor implements JavaScriptExecutor {
35
36 private final transient WeakReference<WebClient> webClient_;
37 private final transient List<WeakReference<JavaScriptJobManager>> jobManagerList_;
38
39 private final transient AtomicBoolean shutdown_;
40
41 private transient Thread eventLoopThread_;
42
43
44 private static final Log LOG = LogFactory.getLog(DefaultJavaScriptExecutor.class);
45
46
47
48
49
50
51 public DefaultJavaScriptExecutor(final WebClient webClient) {
52 webClient_ = new WeakReference<>(webClient);
53 jobManagerList_ = new ArrayList<>();
54 shutdown_ = new AtomicBoolean();
55 }
56
57
58
59
60 protected void startThreadIfNeeded() {
61 if (eventLoopThread_ == null) {
62 eventLoopThread_ = new Thread(this, getThreadName());
63 eventLoopThread_.setDaemon(true);
64 eventLoopThread_.start();
65 }
66 }
67
68
69
70
71
72 protected String getThreadName() {
73 if (shutdown_.get()) {
74 return "Stopped JS executor for " + webClient_.get();
75 }
76 return "JS executor for " + webClient_.get();
77 }
78
79 @SuppressWarnings("deprecation")
80 private void killThread() {
81 if (eventLoopThread_ == null) {
82 return;
83 }
84
85 try {
86 eventLoopThread_.interrupt();
87 eventLoopThread_.join(10_000);
88 }
89 catch (final InterruptedException e) {
90 LOG.warn("InterruptedException while waiting for the eventLoop thread to join", e);
91
92
93 Thread.currentThread().interrupt();
94 }
95
96 if (eventLoopThread_.isAlive()) {
97 if (LOG.isWarnEnabled()) {
98 LOG.warn("Event loop thread "
99 + eventLoopThread_.getName()
100 + " still alive at "
101 + System.currentTimeMillis());
102 LOG.warn("Event loop thread will be stopped");
103 }
104
105
106 try {
107 eventLoopThread_.stop();
108 }
109 catch (final Exception e) {
110 LOG.warn("JS thread did not interrupt after 10s, maybe there is an endless loop."
111 + "Please consider setting an JavaScriptTimeout for the WebClient.", e);
112 }
113 }
114 }
115
116
117
118
119
120 protected JavaScriptJobManager getJobManagerWithEarliestJob() {
121 JavaScriptJobManager javaScriptJobManager = null;
122 JavaScriptJob earliestJob = null;
123
124 synchronized (jobManagerList_) {
125
126 for (final WeakReference<JavaScriptJobManager> weakReference : jobManagerList_) {
127 final JavaScriptJobManager jobManager = weakReference.get();
128 if (jobManager != null) {
129 final JavaScriptJob newJob = jobManager.getEarliestJob();
130 if (newJob != null && (earliestJob == null || earliestJob.compareTo(newJob) > 0)) {
131 earliestJob = newJob;
132 javaScriptJobManager = jobManager;
133 }
134 }
135 }
136 }
137 return javaScriptJobManager;
138 }
139
140
141 @Override
142 public void run() {
143 final boolean trace = LOG.isTraceEnabled();
144
145
146 final long sleepInterval = 10;
147 while (!shutdown_.get() && !Thread.currentThread().isInterrupted() && webClient_.get() != null) {
148 final JavaScriptJobManager jobManager = getJobManagerWithEarliestJob();
149
150 if (jobManager != null) {
151 final JavaScriptJob earliestJob = jobManager.getEarliestJob();
152 if (earliestJob != null) {
153 final long waitTime = earliestJob.getTargetExecutionTime() - System.currentTimeMillis();
154
155
156 if (waitTime < 1) {
157
158 if (trace) {
159 LOG.trace("started executing job at " + System.currentTimeMillis());
160 }
161 jobManager.runSingleJob(earliestJob);
162 if (trace) {
163 LOG.trace("stopped executing job at " + System.currentTimeMillis());
164 }
165
166
167 continue;
168 }
169 }
170 }
171
172
173 if (shutdown_.get() || Thread.currentThread().isInterrupted() || webClient_.get() == null) {
174 break;
175 }
176
177
178 try {
179 Thread.sleep(sleepInterval);
180 }
181 catch (final InterruptedException e) {
182
183 Thread.currentThread().interrupt();
184
185 break;
186 }
187 }
188 }
189
190
191
192
193
194 @Override
195 public void addWindow(final WebWindow newWindow) {
196 final JavaScriptJobManager jobManager = newWindow.getJobManager();
197 if (jobManager != null) {
198 updateJobMangerList(jobManager);
199 startThreadIfNeeded();
200 }
201 }
202
203 private void updateJobMangerList(final JavaScriptJobManager newJobManager) {
204 final List<WeakReference<JavaScriptJobManager>> managers = new ArrayList<>(jobManagerList_.size());
205 synchronized (jobManagerList_) {
206 for (final WeakReference<JavaScriptJobManager> weakReference : jobManagerList_) {
207 final JavaScriptJobManager manager = weakReference.get();
208 if (newJobManager == manager) {
209 return;
210 }
211 if (null != weakReference.get()) {
212 managers.add(weakReference);
213 }
214 }
215
216 managers.add(new WeakReference<>(newJobManager));
217
218 jobManagerList_.clear();
219 jobManagerList_.addAll(managers);
220 }
221 }
222
223
224 @Override
225 public void shutdown() {
226 shutdown_.set(true);
227 killThread();
228
229 webClient_.clear();
230 synchronized (jobManagerList_) {
231 jobManagerList_.clear();
232 }
233 }
234 }