Merge lp:~ralf-claussnitzer-deactivatedaccount/goobi-production/bug-1040672 into lp:goobi-production/1.8

Proposed by Ralf Claussnitzer
Status: Merged
Merged at revision: 100
Proposed branch: lp:~ralf-claussnitzer-deactivatedaccount/goobi-production/bug-1040672
Merge into: lp:goobi-production/1.8
Diff against target: 313 lines (+254/-14)
4 files modified
src/de/sub/goobi/helper/HelperSchritte.java (+20/-11)
src/de/sub/goobi/helper/ScriptThread.java (+0/-3)
src/org/goobi/thread/Supervisor.java (+123/-0)
test/src/org/goobi/thread/SupervisorTest.java (+111/-0)
To merge this branch: bzr merge lp:~ralf-claussnitzer-deactivatedaccount/goobi-production/bug-1040672
Reviewer Review Type Date Requested Status
Henning Gerhardt Approve
Review via email: mp+121602@code.launchpad.net

This proposal supersedes a proposal from 2012-08-28.

To post a comment you must log in.
Revision history for this message
Henning Gerhardt (henning-gerhardt) wrote : Posted in a previous version of this proposal

Needs some improvements:

- Supervisor thread is started even there are no script steps
- yield waiting time can be negative which let sleep() later throw an exception

review: Needs Fixing
Revision history for this message
Henning Gerhardt (henning-gerhardt) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/de/sub/goobi/helper/HelperSchritte.java'
2--- src/de/sub/goobi/helper/HelperSchritte.java 2012-04-24 12:00:57 +0000
3+++ src/de/sub/goobi/helper/HelperSchritte.java 2012-08-28 12:49:20 +0000
4@@ -30,6 +30,7 @@
5 import java.util.List;
6
7 import org.apache.log4j.Logger;
8+import org.goobi.thread.Supervisor;
9 import org.hibernate.Session;
10 import org.hibernate.criterion.Order;
11 import org.hibernate.criterion.Restrictions;
12@@ -119,22 +120,30 @@
13 } catch (DAOException e) {
14 }
15
16- if (automatic) {
17- try {
18- session.close();
19- } catch (Exception e) {
20- }
21- }
22 /*
23 * -------------------------------- zum Schluss alle automatischen Schritte nehmen und deren Automatik ausführen
24 * --------------------------------
25 */
26- // TODO: Don't use iterators, use for loops instead
27- for (Iterator<Schritt> iter = automatischeSchritte.iterator(); iter.hasNext();) {
28- Schritt myStep = iter.next();
29- ScriptThread myThread = new ScriptThread(myStep);
30- myThread.start();
31+
32+ if (!automatischeSchritte.isEmpty()) {
33+ Supervisor scriptThreadSupervisor = new Supervisor();
34+
35+ final Session sessionRef = session;
36+ scriptThreadSupervisor.ifAllTerminatedRun(new Runnable() {
37+ public void run() {
38+ if (sessionRef.isOpen() && sessionRef.isDirty()) {
39+ sessionRef.flush();
40+ }
41+ }
42+ });
43+
44+ for (Schritt step: automatischeSchritte) {
45+ scriptThreadSupervisor.addChild(new ScriptThread(step));
46+ }
47+
48+ scriptThreadSupervisor.start();
49 }
50+
51 }
52
53 /**
54
55=== modified file 'src/de/sub/goobi/helper/ScriptThread.java'
56--- src/de/sub/goobi/helper/ScriptThread.java 2012-02-22 07:43:02 +0000
57+++ src/de/sub/goobi/helper/ScriptThread.java 2012-08-28 12:49:20 +0000
58@@ -46,9 +46,6 @@
59 try {
60 boolean automatic = mySchritt.isTypAutomatisch();
61 hs.executeAllScripts(mySchritt, automatic);
62- if (automatic) {
63- Helper.getHibernateSession().close();
64- }
65 } catch (SwapException e) {
66 logger.error(e);
67 } catch (DAOException e) {
68
69=== added directory 'src/org/goobi/thread'
70=== added file 'src/org/goobi/thread/Supervisor.java'
71--- src/org/goobi/thread/Supervisor.java 1970-01-01 00:00:00 +0000
72+++ src/org/goobi/thread/Supervisor.java 2012-08-28 12:49:20 +0000
73@@ -0,0 +1,123 @@
74+/*
75+ * This file is part of the Goobi Application - a Workflow tool for the support of
76+ * mass digitization.
77+ *
78+ * Visit the websites for more information.
79+ * - http://gdz.sub.uni-goettingen.de
80+ * - http://www.goobi.org
81+ * - http://launchpad.net/goobi-production
82+ *
83+ * This program is free software; you can redistribute it and/or modify it under
84+ * the terms of the GNU General Public License as published by the Free Software
85+ * Foundation; either version 2 of the License, or (at your option) any later
86+ * version.
87+ *
88+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
89+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
90+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
91+ * should have received a copy of the GNU General Public License along with this
92+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
93+ * Suite 330, Boston, MA 02111-1307 USA
94+ */
95+
96+package org.goobi.thread;
97+
98+import java.util.concurrent.CopyOnWriteArrayList;
99+import java.util.List;
100+
101+/**
102+ * Thread supervisor that triggers code if all child threads have terminated.
103+ *
104+ * The supervisor is watching a list of child threads and executes a passed
105+ * Runnable object if all child threads have finished execution. It in turn
106+ * starts all child threads that have not been started when the supervisor starts.
107+ *
108+ * If all child threads have finished execution, the supervisor may start a given
109+ * Runnable implementation and then itself ends its execution.
110+ */
111+public class Supervisor extends Thread {
112+
113+ private List<Thread> threads = new CopyOnWriteArrayList<Thread>();
114+
115+ private Runnable onAllTerminated = null;
116+
117+ private int yieldWaitTime = 1000;
118+
119+ /**
120+ * Add a child thread to the watch list.
121+ *
122+ * @param child Child thread to watch.
123+ */
124+ public void addChild(Thread child) {
125+ threads.add(child);
126+ }
127+
128+ /**
129+ * Set time in milliseconds that delay the supervisor execution after it
130+ * has called yield() to enable other threads to run.
131+ *
132+ * The supervisor sleeps for this time span until it watches the list of
133+ * child threads again. Default yield wait time is 1000 milliseconds.
134+ * Setting the wait time to zero will let the supervisor thread spin
135+ * useless CPU cycles polling the child thread list.
136+ *
137+ * @param millis Yield wait time in milliseconds.
138+ * @throws IllegalArgumentException if the value of millis is negative.
139+ */
140+ public void setYieldWaitTime(int millis) {
141+ if (millis < 0) {
142+ throw new IllegalArgumentException("Length of time to wait cannot be negative.");
143+ }
144+ yieldWaitTime = millis;
145+ }
146+
147+ /**
148+ * Set up a Runnable implementation that gets executed when all child
149+ * threads have terminated.
150+ *
151+ * The passed Runnable is executed only once.
152+ *
153+ * @param r java.lang.Runnable implememtation
154+ */
155+ public void ifAllTerminatedRun(Runnable r) {
156+ onAllTerminated = r;
157+ }
158+
159+ /**
160+ * Start watching the list of child threads and execute a given Runnable
161+ * when all child threads have reached termination state.
162+ *
163+ * Child threads that have not been started yet get started here.
164+ */
165+ public void run() {
166+ while (true) {
167+ for(Thread t: threads) {
168+ switch (t.getState()) {
169+ case NEW:
170+ t.start();
171+ break;
172+ case TERMINATED:
173+ threads.remove(t);
174+ break;
175+ }
176+ }
177+ if (threads.isEmpty()) {
178+ if (onAllTerminated != null) {
179+ onAllTerminated.run();
180+ }
181+ break;
182+ }
183+ yieldFor(yieldWaitTime);
184+ }
185+ }
186+
187+ private void yieldFor(int millis) {
188+ try {
189+ yield();
190+ sleep(millis);
191+ } catch (InterruptedException ire) {
192+ }
193+ }
194+
195+}
196+
197
198=== added directory 'test/src/org/goobi/thread'
199=== added file 'test/src/org/goobi/thread/SupervisorTest.java'
200--- test/src/org/goobi/thread/SupervisorTest.java 1970-01-01 00:00:00 +0000
201+++ test/src/org/goobi/thread/SupervisorTest.java 2012-08-28 12:49:20 +0000
202@@ -0,0 +1,111 @@
203+/*
204+ * This file is part of the Goobi Application - a Workflow tool for the support of
205+ * mass digitization.
206+ *
207+ * Visit the websites for more information.
208+ * - http://gdz.sub.uni-goettingen.de
209+ * - http://www.goobi.org
210+ * - http://launchpad.net/goobi-production
211+ *
212+ * This program is free software; you can redistribute it and/or modify it under
213+ * the terms of the GNU General Public License as published by the Free Software
214+ * Foundation; either version 2 of the License, or (at your option) any later
215+ * version.
216+ *
217+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
218+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
219+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
220+ * should have received a copy of the GNU General Public License along with this
221+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
222+ * Suite 330, Boston, MA 02111-1307 USA
223+ */
224+
225+package org.goobi.thread;
226+
227+import org.junit.Test;
228+
229+import static org.junit.Assert.*;
230+
231+public class SupervisorTest {
232+
233+ @Test
234+ public void canBeStarted() {
235+ Supervisor sv = new Supervisor();
236+ assertEquals("Supervisor thread should be in NEW state.", Thread.State.NEW, sv.getState());
237+ }
238+
239+ @Test(expected = IllegalArgumentException.class)
240+ public void throwsIllegalArgumentExceptionIfGivenNegativeYieldWaitTime() {
241+ Supervisor sv = new Supervisor();
242+ sv.setYieldWaitTime(-1);
243+ }
244+
245+ @Test
246+ public void terminatesWhenStartedWithoutChildThreads()
247+ throws InterruptedException {
248+ Supervisor sv = new Supervisor();
249+ sv.setYieldWaitTime(100);
250+ sv.start();
251+
252+ Thread.sleep(200);
253+
254+ assertEquals("Supervisor thread should be in TERMINATED state.", Thread.State.TERMINATED, sv.getState());
255+ }
256+
257+ @Test
258+ public void addedChildThreadShouldRemainInNewState() {
259+ Supervisor sv = new Supervisor();
260+
261+ Thread child = new Thread();
262+ sv.addChild(child);
263+
264+ assertEquals("Child thread should be in NEW state.", Thread.State.NEW, child.getState());
265+ }
266+
267+ @Test
268+ public void runsChildThreads()
269+ throws InterruptedException {
270+ Supervisor sv = new Supervisor();
271+
272+ Thread child = new Thread();
273+ sv.addChild(child);
274+ sv.setYieldWaitTime(100);
275+ sv.start();
276+
277+ Thread.sleep(200);
278+
279+ assertEquals("Child thread should have been run.", Thread.State.TERMINATED, child.getState());
280+ }
281+
282+ @Test
283+ public void runsHandlerWhenAllChildThreadsTerminated()
284+ throws InterruptedException {
285+ Supervisor sv = new Supervisor();
286+
287+ final Trigger trigger = new Trigger();
288+
289+ sv.addChild(new Thread());
290+ sv.addChild(new Thread());
291+ sv.addChild(new Thread());
292+
293+ sv.ifAllTerminatedRun(new Runnable() { public void run() { trigger.pull(); } } );
294+ sv.setYieldWaitTime(100);
295+ sv.start();
296+
297+ Thread.sleep(200);
298+
299+ assertTrue("Supervisor has not triggered Runnable on child termination.", trigger.hasBeenPulled());
300+ }
301+
302+ private class Trigger {
303+ private Boolean pulled = false;
304+ public void pull() {
305+ pulled = true;
306+ }
307+ public Boolean hasBeenPulled() {
308+ return pulled;
309+ }
310+ }
311+
312+}
313+

Subscribers

People subscribed via source and target branches

to all changes: