Skip to content

Commit 598d3e8

Browse files
committed
termination of scripts using the SIGTERM signal
1 parent 3d7146a commit 598d3e8

File tree

12 files changed

+80
-98
lines changed

12 files changed

+80
-98
lines changed

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,26 @@ in the documentation of this project are to be interpreted as described in [RFC
4747
## Quick Start
4848
These are the basic steps for building your first PEB-based serverless application:
4949

50-
* **1.** Write your Perl application reading user input on STDIN:
50+
* **1.** Download PEB.
51+
* **2.** Select your Perl distribution.
52+
* **3.** Write your Perl application reading user input on STDIN:
5153

5254
```perl
5355
my $input = <STDIN>;
5456
chomp $input;
5557
```
5658

57-
* **2.** Write an appropriate HTML interface with a [settings JavaScript object](./doc/SETTINGS.md#perl-scripts-api) for your Perl application.
59+
* **4.** Write an appropriate HTML interface with a [settings JavaScript object](./doc/SETTINGS.md#perl-scripts-api) for your Perl application.
5860
Triggering a Perl script from a local HTML page is configured using [one of the three possible methods](./doc/SETTINGS.md#perl-scripts-api).
5961
[Selecting files or folders with their full paths](./doc/SETTINGS.md#selecting-files-and-folders) is also possible.
6062

6163
These are the basic steps for building your first PEB-based application using a local Perl server:
6264

63-
* **1.** Write your Perl server application.
64-
* **2.** Write [local-server.json](./doc/SETTINGS.md#starting-local-server) for your local Perl server.
65-
* **3.** Write an appropriate HTML interface and connect it to your server application by traditional web communication protocols.
65+
* **1.** Download PEB.
66+
* **2.** Select your Perl distribution.
67+
* **3.** Write your Perl server application.
68+
* **4.** Write [local-server.json](./doc/SETTINGS.md#starting-local-server) for your local Perl server.
69+
* **5.** Write an appropriate HTML interface and connect it to your server application by traditional web communication protocols.
6670

6771
## Design Objectives
6872
* **1. Easy and beautiful graphical user interface for Perl 5 desktop applications**

doc/CONSTANTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ A typical ``{PEB_executable_directory}`` looks like this:
5252
PEB application directory pathname is compatible with the [Electron](http://electron.atom.io/) framework.
5353
[Epigraphista](https://github.com/ddmitov/epigraphista) is an application which is runnable by both PEB and [Electron](http://electron.atom.io/).
5454

55-
* **Entry File:**
55+
* **Start File:**
5656
PEB starts with one of the following files:
5757
* ``{PEB_executable_directory}/resources/app/index.html``
5858
* ``{PEB_executable_directory}/resources/app/local-server.json``

doc/SETTINGS.md

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,27 +61,27 @@ Every Perl script run by PEB has a JavaScript settings object with an arbitrary
6161

6262
There are three methods to start a local Perl script:
6363

64-
* **Clicking a link to a settings pseudo filename:**
64+
* **Clicking a link to a script settings pseudo filename:**
6565

6666
```html
6767
<a href="test.script">Start Perl script</a>
6868
```
6969

70-
* **Submitting a form to a settings pseudo filename:**
70+
* **Submitting a form to a script settings pseudo filename:**
7171

7272
```html
7373
<form action="test.script">
7474
<input type="submit" value="Start Perl script">
7575
</form>
7676
```
7777

78-
* **Calling a JavaScript function with a settings pseudo filename:**
78+
* **Calling a JavaScript function with a script settings pseudo filename:**
7979

8080
```javascript
8181
peb.startScript('test.script');
8282
```
8383

84-
This method creates an invisible form and submits it to the settings pseudo filename.
84+
This method creates an invisible form and submits it to the script settings pseudo filename.
8585

8686
A minimal example of a JavaScript settings object for a Perl script run by PEB:
8787

@@ -131,20 +131,32 @@ A JavaScript settings object for a Perl script run by PEB has the following prop
131131
}
132132
```
133133

134-
* **scriptExitCommand**
135-
``String`` containing the command used to gracefully shut down [an interactive Perl script](#interactive-perl-scripts) when PEB is closed
136-
Upon receiving it, an interactive script must start its shutdown procedure.
137-
138-
* **scriptExitConfirmation**
139-
``String`` used to signal PEB that [an interactive Perl script](#interactive-perl-scripts) completed its shutdown
140-
All interactive scripts must exit in 3 seconds after ``scriptExitCommand`` is given or any unresponsive scripts will be killed and PEB will exit.
141-
142134
## Interactive Perl Scripts
143-
Each PEB interactive Perl script must have its own event loop waiting constantly for new data on STDIN for a bidirectional connection with PEB. Many interactive scripts can be started simultaneously in one browser window. One script may be started in many instances, provided that it has a JavaScript settings object with an unique name. Interactive scripts must also have the ``scriptExitCommand`` object property. The ``scriptExitConfirmation`` object property is not mandatory, but highly recommended for a quick shutdown of PEB.
135+
Each PEB interactive Perl script must have its own event loop waiting constantly for new data on STDIN for a bidirectional connection with PEB. Many interactive scripts can be started simultaneously in one browser window. One script may be started in many instances, provided that it has a JavaScript settings object with an unique name.
144136

145137
Please note that interactive Perl scripts are not supported by the Windows builds of PEB.
146138

147-
Please also note that if a PEB instance crashes, it will leave its interactive scripts as zombie processes and they will start consuming large amounts of memory! Exhaustive stability testing has to be done when interactive scripts are selected for use with PEB! Even during normal operation interactive scripts use more memory than non-interactive scripts and their use should be carefully considered.
139+
PEB interactive scripts should have ``$|=1;`` among their first lines to disable the built-in buffering of the Perl interpreter, which prevents any output before the script has ended.
140+
141+
When PEB is closing, it sends the ``SIGTERM`` signal to all interactive scripts to prevent them from becoming zombie processes. All interactive scripts must exit in 3 seconds after the ``SIGTERM`` signal is given by PEB.
142+
All unresponsive scripts are killed before PEB exits. The ``SIGTERM`` signal may be handled by any interactive script for a graceful shutdown using the following code:
143+
144+
```perl
145+
$SIG{TERM} = sub {
146+
# your shutdown code goes here...
147+
exit();
148+
};
149+
```
150+
151+
If a PEB instance crashes, it can still leave its interactive scripts as zombie processes consuming large amounts of memory. To prevent this scenario, all interactive scripts should test for a successful ``print`` on the STDOUT. This could be implemented using the following code:
152+
153+
```perl
154+
print "output string" or shutdown_procedure();
155+
156+
sub shutdown_procedure {
157+
exit();
158+
}
159+
```
148160

149161
The following code shows how to start an interactive Perl script right after a local page is loaded:
150162

@@ -169,8 +181,6 @@ The following code shows how to start an interactive Perl script right after a l
169181
var container = document.getElementById('interactive-script-output');
170182
container.innerText = stdout;
171183
}
172-
interactive_script.scriptExitCommand = '_close_';
173-
interactive_script.scriptExitConfirmation = '_closed_';
174184
</script>
175185
</head>
176186

resources/app/index.html

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@
3434
var target = document.getElementById('instance-one-output');
3535
target.innerHTML = stdout;
3636
}
37-
interactive_one.scriptExitCommand = '_close_';
38-
interactive_one.scriptExitConfirmation = '_closed_';
3937

4038
var interactive_two = {};
4139
interactive_two.scriptRelativePath = 'perl/interactive.pl';
@@ -48,8 +46,6 @@
4846
var target = document.getElementById('instance-two-output');
4947
target.innerHTML = stdout;
5048
}
51-
interactive_two.scriptExitCommand = '_close_';
52-
interactive_two.scriptExitConfirmation = '_closed_';
5349

5450
var open_files = {};
5551
open_files.scriptRelativePath = 'perl/open-files.pl';

resources/app/perl/interactive.pl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
exit 0;
1515
}
1616

17+
# This code was used to test handling of the SIGTERM signal from a Perl script:
18+
# $SIG{TERM} = sub {
19+
# print "Terminating interactive script...";
20+
# sleep(2);
21+
# exit();
22+
# };
23+
1724
# Disable built-in buffering:
1825
$| = 1;
1926

@@ -45,13 +52,6 @@
4552
my $stdin = <STDIN>;
4653
chomp $stdin;
4754

48-
# Close after close commmand is received,
49-
# but first print a confirmation for a normal exit.
50-
if ($stdin =~ "_close_") {
51-
print "_closed_";
52-
shutdown_procedure();
53-
}
54-
5555
# Read input text from STDIN:
5656
my (@pairs, $pair, $name, $value);
5757
@pairs = split(/&/, $stdin);

resources/data/readme.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
This folder should contain any data files.
1+
This directory may contain any writable files used or produced by a PEB-based application.

src/main.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ int main(int argc, char **argv)
301301
// ==============================
302302
// Basic program information:
303303
// ==============================
304-
qDebug() << "Application path:" << application.applicationFilePath();
305304
qDebug() << "Application version:"
306305
<< application.applicationVersion().toLatin1().constData();
307306
qDebug() << "Qt version:" << QT_VERSION_STR;
@@ -314,7 +313,7 @@ int main(int argc, char **argv)
314313
mainWindow.setCentralWidget(mainWindow.webViewWidget);
315314

316315
// ==============================
317-
// Entry file:
316+
// Start file:
318317
// ==============================
319318
bool startFileFound = false;
320319

@@ -372,8 +371,6 @@ int main(int argc, char **argv)
372371
if (localServerFile.exists()) {
373372
localServerFullPath = localServerFullPathSetting;
374373
localServerCommandLine.append(localServerFullPath);
375-
qDebug() << "Local server full path:"
376-
<< localServerFullPath;
377374
} else {
378375
mainWindow.qDisplayError(
379376
QString("Local server file is not found."));
@@ -404,7 +401,6 @@ int main(int argc, char **argv)
404401
if (portScanner->portScannerError.length() == 0) {
405402
port = QString::number(portScanner->port);
406403
application.setProperty("port", port);
407-
qDebug() << "Local server port:" << port;
408404
}
409405

410406
if (portScanner->portScannerError.length() > 0) {
@@ -430,7 +426,6 @@ int main(int argc, char **argv)
430426
localServerJson["shutdown_command"].toString();
431427
if (shutdownCommand.length() > 0) {;
432428
application.setProperty("shutdown_command", shutdownCommand);
433-
qDebug() << "Local server shutdown command:" << shutdownCommand;
434429
}
435430
}
436431

@@ -462,7 +457,7 @@ int main(int argc, char **argv)
462457
}
463458
}
464459

465-
// No entry point:
460+
// No start file:
466461
if (startFileFound == false) {
467462
mainWindow.qDisplayError(
468463
QString("No start page or local server is found."));

src/script-handler.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,6 @@ QScriptHandler::QScriptHandler(QJsonObject scriptJsonObject)
5151
scriptInput = scriptJsonObject["scriptInput"].toString();
5252
}
5353

54-
if (scriptJsonObject["scriptExitCommand"].toString().length() > 0) {
55-
scriptExitCommand = scriptJsonObject["scriptExitCommand"].toString();
56-
}
57-
58-
if (scriptJsonObject["scriptExitConfirmation"].toString().length() > 0) {
59-
scriptExitConfirmation =
60-
scriptJsonObject["scriptExitConfirmation"].toString();
61-
}
62-
6354
// Signals and slots for local long running Perl scripts:
6455
QObject::connect(&scriptProcess, SIGNAL(readyReadStandardOutput()),
6556
this, SLOT(qScriptOutputSlot()));

src/script-handler.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ public slots:
4141
QString output = scriptProcess.readAllStandardOutput();
4242
scriptAccumulatedOutput.append(output);
4343

44-
if (output != scriptExitConfirmation) {
45-
emit displayScriptOutputSignal(scriptId, output);
46-
}
44+
emit displayScriptOutputSignal(scriptId, output);
4745
}
4846

4947
void qScriptErrorsSlot()
@@ -67,11 +65,8 @@ public slots:
6765
QProcess scriptProcess;
6866
QString scriptId;
6967
QString scriptFullFilePath;
70-
QString scriptStdout;
7168
QString scriptAccumulatedOutput;
7269
QString scriptAccumulatedErrors;
73-
QString scriptExitCommand;
74-
QString scriptExitConfirmation;
7570
};
7671

7772
#endif // SCRIPTHANDLER_H

src/webengine-page.h

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -341,33 +341,28 @@ public slots:
341341
closeRequested = true;
342342

343343
if (!runningScripts.isEmpty()) {
344-
int maximumTimeMilliseconds = 3000;
345-
QTimer::singleShot(maximumTimeMilliseconds,
346-
this,
347-
SLOT(qScriptsTimeoutSlot()));
348-
349344
QHash<QString, QScriptHandler*>::iterator iterator;
350345
for (iterator = runningScripts.begin();
351346
iterator != runningScripts.end();
352347
++iterator) {
353348
QScriptHandler *handler = iterator.value();
354349

355-
if (handler->scriptExitCommand.length() == 0 and
356-
handler->scriptProcess.isOpen()) {
357-
handler->scriptProcess.kill();
358-
}
359-
360-
if (handler->scriptExitCommand.length() > 0) {
361-
QByteArray scriptExitCommand;
362-
scriptExitCommand.append(
363-
QString(handler->scriptExitCommand).toLatin1());
364-
scriptExitCommand.append(QString("\n").toLatin1());
350+
if (handler->scriptProcess.isOpen()) {
351+
#ifndef Q_OS_WIN
352+
handler->scriptProcess.terminate();
353+
#endif
365354

366-
if (handler->scriptProcess.isOpen()) {
367-
handler->scriptProcess.write(scriptExitCommand);
368-
}
355+
#ifdef Q_OS_WIN
356+
handler->scriptProcess.kill();
357+
#endif
369358
}
370359
}
360+
361+
#ifndef Q_OS_WIN
362+
int maximumTimeMilliseconds = 3000;
363+
QTimer::singleShot(maximumTimeMilliseconds,
364+
this, SLOT(qScriptsTimeoutSlot()));
365+
#endif
371366
}
372367

373368
if (runningScripts.isEmpty()) {
@@ -377,6 +372,7 @@ public slots:
377372

378373
void qScriptsTimeoutSlot()
379374
{
375+
#ifndef Q_OS_WIN
380376
if (!runningScripts.isEmpty()) {
381377
QHash<QString, QScriptHandler*>::iterator iterator;
382378
for (iterator = runningScripts.begin();
@@ -387,15 +383,14 @@ public slots:
387383
if (handler->scriptProcess.isOpen()) {
388384
handler->scriptProcess.kill();
389385

390-
qDebug() << "Interactive script"
391-
<< "timed out after close command and"
392-
<< "was killed:"
386+
qDebug() << "Unresponsive script was killed:"
393387
<< handler->scriptFullFilePath;
394388
}
395389
}
396390
}
397391

398392
emit closeWindowSignal();
393+
#endif
399394
}
400395

401396
protected:

0 commit comments

Comments
 (0)