Saturday, March 20. 2010
WshShell.Exec Considered Harmful Due To Blocking
For the unfamiliar, the Exec method of WshShell (used in Windows scripting) runs an application and provides access to that application's standard streams as TextStream objects. The problem with this method is that the blocking behavior of these streams is not defined (as noted in the comments on the StdOut property) and, more importantly, is impossible to use safely. The problem, familiar to anyone who has dealt with reading and writing to child programs, is that it is very easy to block while attempting to read from (or write to) the child process. What makes this problem worse is that TextStream provides no method for dealing with blocking; there is no way to set non-blocking mode or to check if input is ready to be read or to check if it would be safe to write (at least, none that I am aware of).
As a demonstration of the problem, consider the following applications. First, the child program (written in C):
#include <stdio.h>
int main(void)
{
for (int i=0; i<10; ++i) {
for (int j=0; j<8192; ++j)
fputc('x', stdout);
for (int j=0; j<8192; ++j)
fputc('x', stderr);
}
return 0;
}
var WShell = new ActiveXObject("WScript.Shell");
var wsexec = WShell.Exec(cmd);
var output = "";
var error = "";
// Keep looping until the program exits
while (wsexec.Status == 0) {
while (!wsexec.StdOut.AtEndOfStream) {
output += wsexec.StdOut.Read(1);
}
while (!wsexec.StdErr.AtEndOfStream) {
error += wsexec.StdErr.Read(1);
}
WScript.Sleep(100);
}
WScript.Echo("Output: " + output);
WScript.Echo("Error Output: " + error);
The solution that I came up with (which has its own drawbacks in terms of performance), is to write the program output to files, then read it back in the script once the program has finished. This solves the deadlock problem at the expense of decreasing the performance by requiring the output to be written to disk as an intermediate step (although the OS may not flush it to the physical disk). The code for this solution is presented below:
/** Run a command, in a separate process and retrieve its output.
*
* This is a safer, slower, alternative to WshShell.Exec that supports
* retrieving the output (to stdout and stderr) only after the command
* has completed execution. It does not support writing to the standard
* input of the command. It's only redeeming quality is that it will
* not cause deadlocks due to the blocking behavior of attempting to read
* from StdOut/StdErr.
*
* @param cmd The name/path of the command to run
* @param winstyle The window style (see WshShell.Run) of the command, or null
* @return An object with an exitcode property set to the exit code of the
* command, an output property set to the string of text written by the
* command to stdout, and an errors property with the string of text written
* by the command to stderr.
*/
function run(cmd) {
var tmpdir = FSO.GetSpecialFolder(2 /* TemporaryFolder */);
if (!/(\\|\/)$/.test(tmpdir))
tmpdir += "\\";
var outfile = tmpdir + FSO.GetTempName();
var errfile = tmpdir + FSO.GetTempName();
// Note: See KB278411 for this recipe
// Note2: See cmd.exe /? for interesting quoting behavior...
var runcmd = '%comspec% /c "' + cmd + ' > "' + outfile + '" 2> "' + errfile + '""';
var wshexec = WShell.Exec(runcmd);
// Write stuff to the standard input of the command (through cmd.exe)
// Note: This will block until the program exits if significant amounts
// of information are written and not read. But no deadlock will occur.
// Note2: This will error if the program has exited
try {
wshexec.StdIn.Write("stuff\n");
} catch (ex) {
WScript.Echo("Unable to write to program.");
}
// Do stuff, or write more stuff while cmd executes, or wait...
while (wshexec.Status == 0)
WScript.Sleep(100);
exitcode = wshexec.ExitCode;
var output = "";
try {
var outfs = FSO.OpenTextFile(outfile, 1 /* ForReading */);
output = outfs.ReadAll();
outfs.Close();
FSO.DeleteFile(outfile);
} catch (ex) { }
var errors = "";
try {
var errfs = FSO.OpenTextFile(errfile, 1 /* ForReading */);
errors = errfs.ReadAll();
errfs.Close();
FSO.DeleteFile(errfile);
} catch (ex) { }
return { exitcode: exitcode, output: output, errors: errors };
}
result = run("dir");
WScript.Echo("Exit Code: " + result.exitcode);
WScript.Echo("Output:\n" + result.output);
WScript.Echo("Error Output:\n" + result.errors);
Remember, Don't ever WshShell.Exec a command directly if you are not sure of its inputs and outputs and your script deadlocking would be a problem.
Monday, February 8. 2010
Configuring Tomcat6 with Eclipse in Debian
After some moderate difficulty (mostly due to bugs 507536 and 552480) I have finally managed to setup Eclipse as a build environment for Apache Tomcat. The process is as follows (Note that $DEVELSERVERDIR
can be anything/anywhere that you have access to as a normal user):
- aptitude install eclipse tomcat6-user
- Install Java EE Development Tools plugin from the Eclipse WPT Project
- tomcat6-instance-create
$DEVELSERVERDIR
- cd
$DEVELSERVERDIR
- ln -s /usr/share/tomcat6/lib
- ln -s /usr/share/tomcat6/bin/bootstrap.jar bin
- cp /var/cache/tomcat6/catalina.policy conf
- Configure tomcat and eclipse to your liking and add the Tomcat instance to the Eclipse servers list
At this point you should be able to import/create projects which can be run on your local server instance. (If not, you are in the same boat I was in this morning... and best of luck to you)
Wednesday, December 30. 2009
HTML Multiple Select Without Ctrl
The HTML select element has received much criticism when used for multiple selection over its usability problems. Although it may be simple enough for experienced users, it must be explained in detail (repeatedly) to the less experienced users, who will always find its behavior to be unintuitive. Recently we have come up against this problem and are attempting to work around it.
For some useful background and some great alternate implementations of multiple-selection see Ryan Cramer's analysis and replacement ideas as well as a jQuery Simple Multi-Select plugin which offers an on-demand replacement for the select multiple elements.
The approach that I took was to attempt to leave the select element alone as much as possible, since it is widely recognized and understood by HTML veterans (and already integrated in huge amounts of legacy code), and make it a little more user-friendly by removing the requirement of holding Ctrl to select multiple options. With the modifications in place, clicking on an option will toggle that option between selected and un-selected, while holding Ctrl or Shift will result in the same behavior as before. The solution is written in JavaScript with jQuery (although there is no reason it couldn't be reworked to remove the dependency on jQuery) and was completed without any browser sniffing (only feature-detection).
The script can be found in the forge on the Improve Multiple Select project page. The script can be directly downloaded from the files section (I suggest against directly linking to it - since the location may change). As always, feedback, suggestions for improvement, bug reports, etc. are greatly appreciated.
Sunday, September 13. 2009
You can't not carry out this action at the present time (Error 2486)
I recently encountered the following error in Microsoft Access 2000:
Apparently this is a catch-all error for any condition where the database engine (or presumably one thread of the engine) is busy completing a task and something else (such as a VBA macro) attempts to perform a function which requires the engine to perform some task. It is horribly difficult to track down, due to the lack of internal debugging interfaces in Access, but check for macros which may be updating visual elements in a form and getting into a loop (for example, OnCurrent actions cascading in a loop or likewise). In my particular case, the error was only triggered when a filter was applied in a form which contained a subform with a field whose default value depended on a field in the parent form. Once I removed the default value from the field in the subform, the error disappeared. (Note also that this only occurred in Access 2000, not Access 2003)
Good luck in tracking down the source of this error if you encounter it.
Thursday, July 23. 2009
Write Conflict Where None Exists
BIT
and was allowed to be NULL
. Changing the columns to BIT NOT NULL
and refreshing the linked table in Access solved the problem.
Update: KB 280730 covers this issue.