source: main/trunk/gli/src/org/greenstone/gatherer/remote/ActionQueue.java@ 34234

Last change on this file since 34234 was 34234, checked in by ak19, 4 years ago

Bugfix I think for the deadlock issue that can occur when a GUI task on client-GLI's remote action queue results in an error attempting to produce a popup also run on the GUI thread. showMessageDialog() for error msgs now wrapped in a SwingUtilities.invokeLater(). This does seem to have fixed the deadlock for the exact example case I encountered, which was the issue fixed in commit revision 34232.

File size: 9.6 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Greenstone Librarian Interface application, part of
5 * the Greenstone digital library suite from the New Zealand Digital
6 * Library Project at the University of Waikato, New Zealand.
7 *
8 * Author: Michael Dewsnip, NZDL Project, University of Waikato
9 *
10 * Copyright (C) 2005 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27
28package org.greenstone.gatherer.remote;
29
30import java.io.*;
31import java.net.*;
32import java.util.*;
33import java.util.zip.*;
34import javax.swing.*;
35import org.greenstone.gatherer.Configuration;
36import org.greenstone.gatherer.DebugStream;
37import org.greenstone.gatherer.Dictionary;
38import org.greenstone.gatherer.FedoraInfo;
39import org.greenstone.gatherer.GAuthenticator;
40import org.greenstone.gatherer.Gatherer;
41import org.greenstone.gatherer.collection.CollectionManager;
42import org.greenstone.gatherer.shell.GShell;
43import org.greenstone.gatherer.util.UnzipTools;
44import org.greenstone.gatherer.util.Utility;
45import org.apache.commons.httpclient.HttpClient;
46import org.apache.commons.httpclient.methods.PostMethod;
47import org.apache.commons.httpclient.methods.GetMethod;
48import org.apache.commons.httpclient.HttpException;
49import org.apache.commons.httpclient.methods.multipart.FilePart;
50import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
51import org.apache.commons.httpclient.methods.multipart.Part;
52import org.apache.commons.httpclient.methods.multipart.*;
53import org.apache.commons.httpclient.params.*;
54import org.apache.commons.httpclient.HttpStatus;
55
56// ----------------------------------------------------------------------------------------------------
57// Part of the RemoteGreenstoneServer's QUEUE LAYER
58// ----------------------------------------------------------------------------------------------------
59
60// Moved here. Previously called RemoteGreenstoneServerActionQueue
61/**
62 * A Thread that maintains a queue of RemoteGreenstoneServer Actions
63 * that are to be executed in FIFO fashion.
64*/
65class ActionQueue extends Thread
66{
67 /** The queue of waiting jobs. */
68 private ArrayList queue = null;
69 private boolean exited;
70
71 public ActionQueue()
72 {
73 super("RemoteGreenstoneServerActionQueue");
74 exited = false;
75 if (Gatherer.isGsdlRemote) {
76 queue = new ArrayList();
77 start();
78 }
79 }
80
81
82 synchronized public void addAction(RemoteGreenstoneServerAction remote_greenstone_server_action)
83 {
84 queue.add(remote_greenstone_server_action);
85 notifyAll();
86 }
87
88
89 synchronized public int size()
90 {
91 return queue.size();
92 }
93
94 synchronized public RemoteGreenstoneServerAction getAction(int i)
95 {
96 return (RemoteGreenstoneServerAction)queue.get(i);
97 }
98
99 synchronized public boolean hasExited() {
100 return exited;
101 }
102
103 synchronized public void clear() {
104 queue.clear();
105 }
106
107 public void run()
108 {
109 boolean exit = false;
110
111 while (!exit) {
112 RemoteGreenstoneServerAction remote_greenstone_server_action = null;
113
114 // Wait until we are notify()ed by addAction that there is a new job on the queue
115 try {
116 if(Gatherer.remoteGreenstoneServer != null) {
117 Gatherer.remoteGreenstoneServer.getProgressBar().setAction(null);
118 }
119 synchronized (this) {
120 while(queue.size() <= 0) {
121 wait(); // wait for queue size to become > 0, which is done by external thread (performAction())
122 }
123 // Now there is at least one job on the queue, get the next in line and process it
124 remote_greenstone_server_action = (RemoteGreenstoneServerAction) queue.get(0); //getAction(0) is already synchronized
125 }
126 } catch (InterruptedException exception) {
127 // It may be that GLI is exiting if we get here
128 exit = true;
129 }
130
131 if(exit) {
132 break;
133 }
134
135 try {
136 remote_greenstone_server_action.perform();
137
138 // No exceptions were thrown, so the action was successful
139 remote_greenstone_server_action.processed_successfully = true;
140 }
141 catch (RemoteGreenstoneServerAction.ActionCancelledException exception) {
142 remote_greenstone_server_action.processed_successfully = false;
143 }
144 catch(java.net.ConnectException exception) {
145 if(exception.getMessage().trim().startsWith("Connection refused")) {
146 exit = true;
147 } else {
148 DebugStream.printStackTrace(exception);
149 }
150 RemoteErrorPopup.invokeLater(exception.getMessage());
151 remote_greenstone_server_action.processed_successfully = false;
152 }
153 catch (FileNotFoundException exception) {
154 // FileNotFoundException happens when there's no GS server at the user-provided
155 // url (the address of gliserver.pl is wrong).
156 exit = true;
157 DebugStream.printStackTrace(exception);
158 RemoteErrorPopup.invokeLater("No gliserver.pl found. " + exception.getMessage());
159 remote_greenstone_server_action.processed_successfully = false;
160 }
161 catch (Exception exception) {
162 DebugStream.printStackTrace(exception);
163 RemoteErrorPopup.invokeLater(exception.getMessage());
164 remote_greenstone_server_action.processed_successfully = false;
165 }
166
167 // We're done with this action, for better or worse
168 try {
169 remote_greenstone_server_action.processed = true;
170 synchronized(remote_greenstone_server_action) {
171 remote_greenstone_server_action.notifyAll(); // notifies RemoteGreenstoneServer.performAction()
172 }
173 } catch (Exception exception) {
174 System.err.println("RemoteGreenstoneServerActionQueue.run() - exception: " + exception);
175 }
176 synchronized (this) { // remove the action just processed from the queue, since it's done.
177 queue.remove(0);
178 }
179 }
180
181 // Out of while, means exit = true
182 // Stop the gazillion annoying error messages when the connection was simply
183 // refused or when the user pressed Cancel in the opening dialog, by clearing
184 // the action queue of subsequent actions and setting exited to true.
185 synchronized(this) {
186 queue.clear();
187 exited = true;
188 }
189 }
190
191
192 // When deadlock, referred to in Kathy's commit comments as stalemate, occurs during a RemoteGS threaded
193 // task run on a queue, and this task is a GUI one and on the GUI thread, such as launching GEMS from the
194 // MetadataSetManager to create/edit a metadata set, any remote errors that then cause error dialogs to
195 // pop up result in a deadlock between the widgets since there's waiting going on for the first widget.
196 // This prevents the second widget from showing or working right, as both are on the same (GUI) thread.
197 // As end result, client-GLI freezes.
198 // An example of such an error that caused an error message to popup resulting in a GUI deadlock was
199 // fixed in commit http://trac.greenstone.org/changeset/34232 (client-GLI > MetadataSetManager > GEMS)
200 // But in theory such a GUI deadlock could happen for as yet unfixed errors. It prevents us from even
201 // reading the error msg in the popup and delays debugging. What we need is to show the popup when
202 // the GUI thread is free, i.e. SwingUtilities.invokeLater() stuff.
203 // Private inner class RemoteErrorPopup deals with that. Refer to:
204 // - https://stackoverflow.com/questions/1595744/is-joptionpane-showmessagedialog-thread-safe
205 // "JOptionPane does not document that it is thread safe, so you have to use invokeLater()."
206 // Also contains SwingWorker-based idea for the GUI thread clash between OpenCollDialog and
207 // ProgressBar during automated Greenstone GUI testing.
208 // - https://stackoverflow.com/questions/13863713/which-thread-to-launch-joptionpane
209 // - https://www.codota.com/code/java/methods/javax.swing.JOptionPane/showMessageDialog
210 // - https://www.programcreek.com/java-api-examples/?class=javax.swing.SwingUtilities&method=invokeLater
211 private static class RemoteErrorPopup implements Runnable {
212
213 /** Custom static method to make a safely threaded popup easier to call */
214 public static void invokeLater(String msg) {
215 SwingUtilities.invokeLater(new RemoteErrorPopup(msg));
216 }
217
218
219 private String message;
220
221 private RemoteErrorPopup(String msg) {
222 message = msg;
223 }
224
225
226 @Override
227 public void run() {
228
229 String msg = Dictionary.get("RemoteGreenstoneServer.Error", message);
230
231 // We often end up with giant bloated remote build error messages when things blow up
232 // These messages appear in pop ups that take up the entire screen and we can't even see the OK button
233 // The following therefore adds a scrollPane around error messages shown in showMessageDialog
234 // https://alvinalexander.com/java/joptionpane-showmessagedialog-example-scrolling/
235 JTextArea textArea = new JTextArea(20, 70);
236 textArea.setText(msg);
237 textArea.setEditable(false);
238 // wrap a scrollpane around message
239 JScrollPane scrollPane = new JScrollPane(textArea);
240
241 JOptionPane.showMessageDialog(Gatherer.g_man, scrollPane, Dictionary.get("RemoteGreenstoneServer.Error_Title"), JOptionPane.ERROR_MESSAGE);
242 }
243 }
244
245}
246
247
Note: See TracBrowser for help on using the repository browser.