Showing posts with label Programming. Show all posts
Showing posts with label Programming. Show all posts

2023-09-20

Running Rules — A Rough Ranking

A caveat: this post is more brain dump than research. I would not be surprised if multiple academic papers have laid out competing hierarchies and frameworks for classifying rules engines. However, doing research is hard. It takes me days of searching to find good papers, and then it takes me weeks of re-reading to grok them. So what follows is entirely NIH.

Problem definition

Say you have a big pile of data. There's a file, it has some records in it. The records are serialized in some format, like json, protobufs, pcaps, or, more ridiculously, binary xml records inside of a chunked binary file format (how I miss LfLe!). Maybe there's some compression involved. And what your boss/department/company/industry would like to know is whether individual records in that data set match any number of rules in a set and, if so, which ones.

Note how uncomplicated of a task this seems. We don't need to sum any of these fields across records, compute statistics on them, join them with lots of other data sets (well, we do and will; cyber calls this phase "enrichment" but it happens much later), or such. Our problem is, for each of n records, which of our m rules matches on the record?

(Note also how this is actually a join, but since we're so often processing complex records in complicated formats as it lies and using rules with a rich and complicated set of predicates, refining the computation to the point where it can be performed as a join is damn near impossible.)

Rules

Ok, records, sure, records are easy. Tuples of fields, maybe there's some typing, maybe the fields have names, maybe there are complex types, but, still, we all understand these records: they're logs, CSV, JSON, maybe protobufs/Avro/Thrift or pcaps. What are the rules?

A rule could be very simple, e.g., "Value = 5". A simple evaluation rule like this can be called a predicate; it's performing a single evaluation against a single field in the record and the result of the predicate is either true or false. Predicates can be joined together into complex rules with Boolean logic. The operators in the predicates will likely permit equality checks and comparisons. Often there will be functions (which are also just operators without fancy syntax) like "contains", "in", "like", "matches", which help with strings and sets. Perhaps if you're unlucky, you'll be working in an extensible rules language where a predicate can involve parsing kilo/megabytes of raw data to compute some fields on the fly. Then you'll tire of such complexity, and the accompanying slowdown in runtime and development time that comes with it, and you'll adopt the hot new rules language that seems simpler and friendlier, the kind assembled by field practitioners who can't be bothered with knowing that describing computer language syntax was solved 64 years ago.

Rules evaluate different fields in the records, often comparing them against literal values. It may be possible for rules to compare different fields in the same record, e.g., "record.created_timestamp < record.lastmodified_timestamp", though this is rare. What we will not consider is the ability for rules to compare/evaluate multiple/different records with possible aggregations. That's what SQL is for.

Implementation types, ranked

So, how do we write a program to run the rules against our data? What follows is a ranking of implementation approaches I can envision, from least optimized to most. This ranking primarily considers throughput, i.e., how many records can evaluated against a given set of rules in a measure of time.

Ad hoc

Rules seem hard and abstract. Unrestricted Boolean logic? Comparisons? Code Monkey think this is hard! (Code Monkey output not functional or elegant.)

If rules can be restricted to simpler and specialized forms, then an ad hoc rules engine may only need to consider hard-coded logic for looking up hash values in a hash set, parsing PE file headers, or conducting string searches. Perhaps a few different types of rules can express a small number of commonly desired Boolean forms, so a rule could simply OR constituent ad hoc evaluation functions, or maybe AND then, or negate them. I did this with EnCase eDiscovery/EnCase Cybersecurity, where the user could choose from three different Boolean forms for combining logic on filesystem metadata, file content search terms, and file hashes. Those three options handled all the practical use cases.

Parse trees

Ad hoc engines are pretty limited, however, and parsers aren't that hard, once you know how to write one. So, if you can parse formal rules, with Boolean logic, then you can build a parse tree data structure. And if you can build a parse tree, then you can execute it according to the types of nodes in the parse tree. The engine will generally involve a stack data structure and one or more switch statements for executing what to do for each type of node in the parse tree (e.g., an OR operator). Sometimes the parse tree may be turned into an abstract form, with enums and objects; other times it'll just be character literals that are being compared directly.

Performance will vary amongst implementations, and especially depend on the implementation language/platform. An engine written in Python will be a lot slower than one written in Rust or C. Still, throughput may only be 10MB/s or less, and will degrade considerably as the rules add up — each rule will need to be parsed and evaluated, and there's usually not any work done to consolidate logic or even just useful setup work between rules. If parsing is not a separate step from evaluation, then the parsing logic itself will need to be re-evaluated with each record.

A possible way out of writing a proper parser and evaluation engine is to recast the rules into some other language that can be embedded and executed itself; I also did this in EnCase eDiscovery/EnCase Cybersecurity where the EnScript engine itself was exposed as an API to EnScript, and then I could just generate EnScript snippets on the fly as EnCase Filters and execute that code against objects. And when I first started thinking about evaluating rules, in 2005, I realized that if the industry was smart it would simply write its rules in Prolog and life would be easy.

Did the industry adopt Prolog?- 

no

(That was a Prolog joke.)

Instead the industry first adopted OpenIOC (yay xml) and then whatever-the-hell YARA is. And now there's sigma, which is even less well-defined than these two predecessors.

Examples: Hayabusa, chainsaw (a bit more elegant)

Virtual machines

If you can generate a parse tree and you refactor your code enough, you'll often find you have a loop that pops nodes off a stack, evaluates those nodes, and then pushes them back onto the stack until the stack contains only one element and there's nothing left in the rule to parse: that one element then is the result.

If you have that code, then it is not a big leap to represent those nodes as invented byte code, or p-code, and then to execute that byte code. As it happens, this leap can make the evaluation engine astonishingly straightforward — there's a loop around decoding the current instruction where the opcode's evaluated by a switch statement and the cases perform the operation. The complexity of direct parse tree evaluation can be spread out across a separate compilation step, which transforms the parse tree into the byte code.

Generally these virtual machines fall into two classes: stack-based and stackless. Stack-based virtual machines are typically simple and low-level, where the operations are extremely simple and evaluation follows the parse tree representation. Stackless VMs may use a byte code representation that is higher-level and domain specific, with instructions that correspond more directly to complicated functions (e.g., "contains" resulting in a string find). This is not a precise distinction, of course; the precise distinction is that stackless VMs lack a stack... but lacking a stack often results in greater complexity being put into the operations.

Virtual machines have some nice properties that bring performance benefits. Decoding and evaluation results in simple code, and optimizing compilers love simple code, which they can tease out into sequences of instructions that pipelined CPUs can evaluate efficiently. There's often far less branching in the resulting code, so there are fewer stalls. And critically, the byte code tends to be compact and stored altogether in memory, resulting in excellent L1 cache hit characteristics.

With a separate compilation step, the virtual machine approach can also be amenable to optimization passes. For example, across a set of rules there may be duplicate predicates. A rule engine that understands all the rules at once could deduplicate evaluation of these predicates. It could also reorder evaluation of predicates to try to prove rules don't match (the common case) with early exits.

Lightgrep uses the virtual machine, in a stackless complicated way. It's based in no small part on Russ Cox's excellent blog post Regular Expression Matching: the Virtual Machine Approach. Whole programming languages can be implemented with virtual machines, too, like Python. Another notable example that's generated considerable work and blog posts over the years is Lua. One could do a lot worse than embedding Lua and calling it a day.

Other examples: jq, PCRE

JITs

If you're parsing rules and compiling them to byte code, well, why not compile them to machine code instead and make it the CPU's problem? This is called a JIT, for just-in-time.

Do you like speed? JITs can be blazing fast. They lower evaluation to the level of the machine, where there is no engine, i.e., getting your crappy Code Monkey software out of the way of the CPU is probably the best way to go fast.

But JITs also have a cost — welcome to tradeoffs! First, rules tend to be fairly compact, a few dozen characters. Those rules can unroll to hundreds or thousands of machine code instructions. Invented virtual machine byte code can still be compact, but machine code deals with registers, raw arithmetic, and loads/stores. So, typically, JITs will need more memory to represent the rules, and this can turn into a problem when you have a lot of rules.

Additionally, compiling the rules to machine code can take time, so there are larger startup costs. This is especially the case if your JIT has optimization passes. This often leads to faster evaluation, but could wind up being a false economy, where a simpler engine would get through processing all the data before a JIT even starts running.

What's more, an off-the-shelf engine with optimizers can itself be dozens of megabytes in size. You're going to embed LLVM into your tool? Good luck fitting that on a floppy and/or downloading to point-of-sale endpoints running on a cheap Comcast link.

Portability also becomes a concern. If you prudently decide not to embed LLVM, now you need to find a JIT that's portable if you want to support more than x86. Good luck! I'm sure there are solutions, but I don't know of a clear answer off the top of my head.

JITs also have security concerns. A normal executable gets compiled and it runs. Modern operating systems will treat its code and data separately, with the code protected by the virtual memory manager as read-only and the data treated as non-executable. With JITs, data and code are the same thing! The JIT will need to write out machine code to memory and then execute it directly; this requires some low-level operating system trickery.

Examples: PCREjit, luajit, redgrep, Ken Thompson's original regular expression search engine for IBM 7049

SIMDized

Modern CPUs have a lot of capability. Pipelined CPUs became common forty years ago. Thirty years ago CPUs went super-scalar. Twenty years ago CPUs went multi-core.

A rule engine that uses a JIT, especially a simple one without optimization passes, will struggle with pipelining as it's likely to embed many branches that mispredict with some regularity (a 90% branch prediction rate is still pretty bad). It will likely not execute with IPC rates > 1.0 (unless you're running on one of Apples magic M chips, that put jillions of transistors towards a monstrous ROB). And for sure you will not get multiple cores utilized, as multithreading is always an exercise left to the user (and rightfully so).

These three limitations need to be addressed in a rules engine if you want to fly as fast as the hardware can go. Good luck! I don't have good answers for you other than lots and lots of hard work.

And even if you are able to write a fantastic rules engine that JITs out code with high branch prediction and IPC rates, and uses threads smartly to process [batches of, you'll need to amortize your locking to avoid stalling] records in parallel, you still won't be using your CPU to its fullest potential.

18 years ago Intel released SSE3, and SSE3 quietly revolutionized SIMD optimization. It took awhile for folks to figure out the magic new pshufb instruction, but each new SIMD ISA since has added lots of capability (though ARM's NEON ISA is more comparable to SSE 4.2 or AVX, with 128 bit registers while just about everyone with an Intel CPU has at least 256 bit registers and many have 512 bit registers). Contemporary CPUs can now often retire multiple SIMD instructions per cycle, if they're scheduled correctly.

Predicates in rules evaluate to true and false. Rules combine predicates with Boolean logic. If you're thinking what I'm thinking, then finding ways to represent predicates and rules in SIMD bit vectors is likely to provide a lot of benefit.

However, it's easy to forget the MD in SIMD. A good SIMD engine will likely only gain high throughput by biting off a chunk of records, decomposing them into their fields, and evaluating those fields with SIMD operations in parallel, then, many instructions later, taking a look at the result and separating matching records.

One major trouble with adopting SIMD is portability. Even amongst x86 CPUs, there are multiple targets ISAs (AVX-512 is a family [and a Tolstoy-esque unhappy one at that] of differing ISAs, then there's AVX-2, AVX, SSE 4.2 for the Sandy Bridge diehards like me...), and different CPU microarchitectures may have very different performance characteristics for the instructions in those ISAs. And then there's ARM NEON. Oy.

Another major trouble with using SIMD operations in a rules engine is that the predicates in a modern cybersecurity ruleset are many, but most evaluate to false a great deal of the time. So a bit vector representation of all the predicates across all the rules will still be large (thousands of bits), much too large for a single register, and most of the bits will be unset. So, there's natural sparsity to cope with — maybe a bit vector representation isn't worth it? Another major trouble will be the dependencies between predicates in the rules; how do you benefit from early exit with a SIMD approach?

The only answer when it comes to SIMD optimization is empirical bloodymindedness.

Examples: none that I know of, naïve SIMDization is an uncanny valley of death

Multi-algorithm SIMD with ad hoc components

Bloodymindedness it is then. Relying on simple SIMD operations to evaluate sparse bit vectors with thousands of bits could lose out to a normal JIT engine, maybe even a VM. The way to apply SIMD is to look for amenable types of predicates and rules and target those with specialized SIMD algorithms.

Here Hyperscan reigns supreme and alone, and with its integration into Suricata pcap scanning has benefited from it for a number of years. Noseyparker also integrates Hyperscan for secrets scanning. But Hyperscan, as an Intel project, is unsurprisingly Intel-only. There's been an attempt or two to port Hyperscan to ARM, but Hyperscan's development itself seems tenuous and the porting attempts can be characterized as "academic" in comparison.

DuckDB is also worthy of mention here. As a columnar database, it is a good fit for SIMD operations, and it benefits greatly from them. Any Python programmer considering writing a rules engine should do everyone a favor and instead focus on ingesting their data into DuckDB. (I would also extend this recommendation to many C programmers who've written popular engines, as well as to those that naïvely bundle such engines into larger scanners.)

Further Considerations

Record parsing

Rules don't exist in a vacuum. After all, what we care about are the records. And records are inevitably represented in their own form (CSV, json, in-memory data structures, etc.). Dealing with those records and getting their necessary fields out to where they can be evaluated by predicates in our ruleset is not free. In fact, it may dominate the runtime, perhaps by orders of magnitude, especially if we're not careful with how the data's encoded (ugh cybox) and which libraries are used (🤡).

An interesting approach is simdjson. Simdjson uses SIMD operations in parsing json records in parallel, but it also embeds some [very] rudimentary rules capability with JSON Pointer support. That allows it to evaluate json records without first doing a full parse.

Avoiding full parsing has of course been a key aspect in regular expression searching. Fast regular expressions don't parse text into lines, parse lines into Unicode codepoints, and run codepoints through their Unicode-aware regexp engines; no, fast regular expression engines look for byte literals in megabyte-sized blocks of bytes and only when there are hits—and some refuse to believe there are hits without repeated evidence—do they bother to look for line breaks and consider the full regular expression(s).

Heavyweight processing components

Additionally, some predicates may be call-outs to entire subcomponents doing complicated things (looking at you, YARA). Searching strings or files, hashing, parsing parts of file formats, these are operations that go well beyond evaluating records of tuples/dicts in csv or json. The rules engine must call out to these subcomponents, and a considerable portion of runtime will be spent in those components and not in the evaluation of rules.

Predicate cost weighting

To repeat, runtime may be dominated in components evaluating complicated predicates completely outside the normal ken of a rules engine. Therefore, the ideal rules engine will have some notion of which types of predicates are costlier than others, and the engine will then analyze the rules and the predicate costs to evaluate the cheapest predicates first that permits early-exit.

For an example of a trick I've used for decades, though not in a general purpose rules engine, consider cryptographic hashing for file matching; if I know the file sizes of the set of [contraband/IP/malware] files I'm trying to match against then I can first check to see whether the input file has a matching file size; only if it does will the input file need to be read and hashed. This will get you several orders of magnitude of speedup when scanning disk images or live endpoints, so long as you have all the original file sizes and their cardinality is not too great.

A Dismal Conclusion

Do I have a conclusion? No, not really. Rule engines exist, but few are separate from their rule language, with tight coupling that consigns them to the same fate as that of the rule language and the commercial entities that sponsor them. Most of the best engines tap out with a virtual machine approach, avoiding the hard engineering involved in JITs and SIMD, but also missing out on the 10+ GB/s of throughput that's possible with contemporary CPUs and DRAM. A few crazy people have made it to that promised land but for specialized domains. And going crazy is meaningless if you don't consider the input format of the records you want to process.

Developing and maintaining an ideal rules engine is a hard problem, requiring genius, expertise, bloodymindedness, and the long-term focus/dedication (with concomitant financing) to scrap existing code and build new implementations from scratch in response to new hardware capabilities. Am I working on one? No. Would I like to? Sort of. Will I? Maybe, but likely without success, because genius, expertise, bloodymindedness, and long-term focus are a lot to ask, and I am lazy, tired, and distracted.

So, continue along with your YARA and SIGMA rules, and their 386-era throughput.

2012-03-21

Don't call virtual functions in a destructor

Sometimes I like to define the outline of an operation in a base class and then have it delegate to virtual functions in derived classes. This is the Template Method design pattern.

class Base {
  uint Counter;


  void foo() {
    ++Counter;
    _foo();
  }


  pure void _foo();
}


class Derived {
  virtual void _foo() {
    Console.WriteLine("In Derived::_foo");
  }
}



Last night I tried to gin up a sort of "stack guard," something that would let me perform an operation once I got to a certain point in a function, but which also would be executed if the function returned early. When something needs to be done at the end of a block of code, I usually define a new class that has code in its destructor. For example, a lock class could take a critical section and acquire it in its constructor and release it in its destructor, and you just need to create the object in the appropriate place to perform your thread-safe operation. I wanted the same thing, but just a bit more complex, where I could call the release function manually once I'd reached a certain point and then be assured it would not be called in the destructor.

class Committer {
  bool Committed;


  ~Committer() {
    if (!Committed) {
      commit();
    }
  }


  void commit() {
    _commit();
  }


  pure void _commit();
}


class SafeDB: Committer {
  SafeDB(Database db): Committer() {
    // open a database connection
  }


  virtual void _commit() {
    // commit any pending transactions
  }
}



Simple enough. You can quibble about whether it's really worth it to define a class hierarchy for such simple functionality, but I've been trying hard to increase the amount of abstraction I use in EnScript to reduce code bloat and time wasted debugging.

The problem is that this doesn't work and generates run-time errors about null references. Why?

Answer: SafeDB's destructor is called first, and then Committer's destructor is called. As part of executing SafeDB's destructor, it nulls out whatever class members are a part of SafeDB before calling Committer's destructor. Hence, by the time you get to Committer's destructor, you really only have a Committer object, not a SafeDB object. The virtual table is still active, however, so the call to SafeDB::_commit() happens successfully, but it finds itself confronted with no class variables. Boom!

So, this is now another little corner of the EnScript world that's been explored. Time to light out for the territory...

2012-01-31

ThreadClass::WAITSAFE

A New Year's Resolution, if you can call it that, is to blog more in the moment, as I'm figuring stuff out. I think I just figured out how the ThreadClass::WAITSAFE option works, so here goes...

ThreadClass::WAITSAFE is an enum value under ThreadClass::WaitOptions. It can be passed to several of the different Wait() methods related to the threading classes. The gloss on it says "Exits early if thread stops running," but there aren't any examples that show it in action and it seems to be the default parameter for some of the Wait() methods, but not for all of them. Specifically, it's not the default parameter for either of the ThreadClass::WaitClass methods.

To test it, we're going to have three threads. One is just going to spin its wheels—it's the thread we'd have doing real work, so we'll call it the "doer." Another thread will launch the doer and wait for it to finish, which we'll call the "waiter." This seems kind of silly, except that in a real application, the waiter could have a pool of doer threads, and could be coordinating their work and that may justify a separate thread of its own (especially if it's happening while you have a dialog up). Finally, we'll have our main thread that creates and launches the waiter, sleeps for a bit, then tells it to cancel. The waiter in turn will cancel the doer thread, and exit once the doer has exited. Here's the code:



class Doer: ThreadClass {
  Doer(): ThreadClass() {}

  virtual void Run() {
    while (IsRunning()) {
      Console.WriteLine("Doer sleeping...");
      SystemClass::Sleep(1000);
    }
    Console.WriteLine("Doer exiting.");
  }
}

class Waiter: ThreadClass {
  Waiter(): ThreadClass() {}

  virtual void Run() {
    Doer d();
    ThreadClass::WaitClass w();
    Console.WriteLine("Starting doer...");
    d.Start();
    w.AddObject(d);
    Console.WriteLine("Waiting on doer...");
    bool waitRet = w.WaitAll();
    Console.WriteLine("Woke up waiter");
    if (!IsRunning()) {
      Console.WriteLine("Waiter was cancelled. Stopping doer...");
      d.StopRunning();
      d.Wait();
    }
    Console.WriteLine("Waiter exiting, waitRet = " + waitRet);
  }
}

class MainClass {
  void Main() {
    SystemClass::ClearConsole();
    Waiter t();
    Console.WriteLine("Starting waiter...");
    t.Start();
    Console.WriteLine("Sleeping for 10 seconds");
    SystemClass::Sleep(10000);
    Console.WriteLine("Cancelling waiter...");
    t.StopRunning();
    Console.WriteLine("Waiting for waiter to quit...");
    t.Wait();
    Console.WriteLine("Waiter returned successfully. Done.");
  }
}

This code produces the following output:


Starting waiter...
Sleeping for 10 seconds
Starting doer...
Waiting on doer...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Cancelling waiter...
Waiting for waiter to quit...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
...


The problem is that this script never terminates successfully. The Waiter class uses WaitAll() to wait for the Doer thread to finish, but the Doer thread is just hanging out, enjoying its infinite loop until someone calls StopRunning() on it... which never happens. This is where ThreadClass::WAITSAFE comes in. Here's some modified code:


class Doer: ThreadClass {
  Doer(): ThreadClass() {}

  virtual void Run() {
    while (IsRunning()) {
      Console.WriteLine("Doer sleeping...");
      SystemClass::Sleep(1000);
    }
    Console.WriteLine("Doer exiting.");
  }
}

class Waiter: ThreadClass {
  Waiter(): ThreadClass() {}

  virtual void Run() {
    Doer d();
    ThreadClass::WaitClass w();
    Console.WriteLine("Starting doer...");
    d.Start();
    w.AddObject(d);
    Console.WriteLine("Waiting on doer...");
    bool waitRet = w.WaitAll(ThreadClass::INFINITE, ThreadClass::WAITSAFE);
    Console.WriteLine("Woke up waiter");
    if (!IsRunning()) {
      Console.WriteLine("Waiter was cancelled. Stopping doer...");
      d.StopRunning();
      d.Wait();
    }
    Console.WriteLine("Waiter exiting, waitRet = " + waitRet);
  }
}

class MainClass {
  void Main() {
    SystemClass::ClearConsole();
    Waiter t();
    Console.WriteLine("Starting waiter...");
    t.Start();
    Console.WriteLine("Sleeping for 10 seconds");
    SystemClass::Sleep(10000);
    Console.WriteLine("Cancelling waiter...");
    t.StopRunning();
    Console.WriteLine("Waiting for waiter to quit...");
    t.Wait();
    Console.WriteLine("Waiter returned successfully. Done.");
  }
}


This time, we pass ThreadClass::INFINITE (the default) and ThreadClass::WAITSAFE (not the default) as parameters to the WaitAll() method in the Waiter. This is what we get for output:


Starting waiter...
Sleeping for 10 seconds
Starting doer...
Waiting on doer...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Cancelling waiter...
Waiting for waiter to quit...
Doer sleeping...
Woke up waiter
Waiter was cancelled. Stopping doer...
Doer exiting.
Waiter exiting, waitRet = 0
Waiter returned successfully. Done.


Huzzah! Calling StopRunning() in the main thread on the waiter causes the waiter to wake up from WakeAll(), now that we're using ThreadClass::WAITSAFE. Once the Waiter wakes up, it checks IsRunning() to check whether it's been given the signal to quit, via StopRunning(), and then does likewise with the Doer thread. The Doer exits gracefully, then the Waiter exits gracefully, and then the whole script exists. Quite nice.

Notice that we've output the return value from WaitAll(), and it seems to have been false in this case. The question this raises is, would it return true if Doer exited before Waiter was cancelled with StopRunning()? Here's some modified code where the Doer exits after about 5 seconds, while the main thread still waits about 10 seconds to tell the Waiter to stop.



class Doer: ThreadClass {
  Doer(): ThreadClass() {}

  virtual void Run() {
    uint i;
    while (IsRunning() && i++ < 5) {
      Console.WriteLine("Doer sleeping...");
      SystemClass::Sleep(1000);
    }
    Console.WriteLine("Doer exiting.");
  }
}

class Waiter: ThreadClass {
  Waiter(): ThreadClass() {}

  virtual void Run() {
    Doer d();
    ThreadClass::WaitClass w();
    Console.WriteLine("Starting doer...");
    d.Start();
    w.AddObject(d);
    Console.WriteLine("Waiting on doer...");
    bool waitRet = w.WaitAll(ThreadClass::INFINITE, ThreadClass::WAITSAFE);
    Console.WriteLine("Woke up waiter");
    if (!IsRunning()) {
      Console.WriteLine("Waiter was cancelled. Stopping doer...");
      d.StopRunning();
      d.Wait();
    }
    Console.WriteLine("Waiter exiting, waitRet = " + waitRet);
  }
}

class MainClass {
  void Main() {
    SystemClass::ClearConsole();
    Waiter t();
    Console.WriteLine("Starting waiter...");
    t.Start();
    Console.WriteLine("Sleeping for 10 seconds");
    SystemClass::Sleep(10000);
    Console.WriteLine("Cancelling waiter...");
    t.StopRunning();
    Console.WriteLine("Waiting for waiter to quit...");
    t.Wait();
    Console.WriteLine("Waiter returned successfully. Done.");
  }
}


And here's the output:


Starting waiter...
Sleeping for 10 seconds
Starting doer...
Waiting on doer...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer sleeping...
Doer exiting.
Woke up waiter
Waiter exiting, waitRet = 1
Cancelling waiter...
Waiting for waiter to quit...
Waiter returned successfully. Done.


So, as you can see, WaitAll() still returns when the Doer thread exits, and this time it returns true. Waiter then exits from its Run() function before the main thread has the chance to cancel it. The upshot is that we can use the return value from WaitAll() to tell whether the thread waiting has had StopRunning() called on it (return value is false) or whether simply the operations it's waiting on have completed (return value is true).

Oh frabjous day, everything works!

2011-08-09

ExecuteClass pitfall

Of the ways that EnScript can interact with other programs, using ExecuteClass to launch programs from the command-line is probably my favorite. It's simple to use, simple to debug, and doesn't leave you with an unholy mess of code, like COM. However, I recently ran into a pitfall with ExecuteClass and hopefully sharing it here will keep others from running into it or, at least, recognizing it when they're at their wits' end.

The problem? ExecuteClass becomes angry when the target program writes a lot of data to stdout or stderr, and you wouldn't like ExecuteClass when it's angry.

ExecuteClass currently has the Output property, a String, for giving you back stdout output from the application. My somewhat shaky belief is that this is updated once the application has ended—I haven't tested this in a while, though. Additionally, notice that there's no property representing stderr.

If the target application you are running generates lots of output to stdout or to stderr, you will find that the application will hang after a while. Why? Well, in the words of the illustrious Beej, "On many systems, pipes will fill up after you write about 10K to them without reading anything out." Once the target application fills up whatever pipe EnCase has connected to stdout or stderr, it will block on the next write to the stream... and EnCase will never consume from the pipe, causing the target application to hang indefinitely.

One possible workaround to this is not to call your target application, but to call cmd.exe instead, with the argument being the command you want to run, redirecting output to a file. I haven't yet tested this, but it should work. The other, of course, is to change the target application to write output to a file itself. As for the underlying win32 calls involved, I found an MSDN article with a sample program demonstrating how child processes are created and how to read data from stdout and stderr.

2011-02-15

Dynamic Features in EnScript

EnScript is first and foremost a static language. It's statically typed and statically compiled, which means that EnScript figures out everything in your script before it starts to execute it, and if it cannot figure out which code should be called or finds any other syntactic problems, it generates a compilation error. It also means that EnScript executes reasonably efficiently, since the compiler has figured out which functions to call in advance, and this is done in a relatively lightweight manner.

(It should be noted that EnScript does not do much in the way of code-optimization, however, so dynamic languages that make use of just-in-time compilation and other aggressive techniques may yet execute faster than EnScript.)

Despite being squarely in the static camp of languages, EnScript does have some features which can be considered "dynamic." By dynamic, I mean that you can write an EnScript that is able to interact with the EnScript engine and script code, at least in some ways.

The dynamic features in EnScript are:
  • Typecasting
  • Class reflection
  • Property accessors
  • Subordinate execution through ProgramClass
These dynamic features make EnScript far more powerful than it initially seems. I'll be writing about these features over the next few weeks. Since typecasting is short and sweet, I'll cover that now.

Upcasting

Let's say you're working with a NameListClass object. Since NameListClass inherits from NodeClass, you can always treat a NameListClass object as a NodeClass object without doing anything special:

NameListClass list();
list.Parse("one two three", " ");

NodeClass nodeRef = list; // up-cast
Console.WriteLine(nodeRef.Count());

This is an example of upcasting, where we treat a derived object as its parent type by manipulating it through a parent-type reference. EnScript knows that NameListClass inherits from NodeClass, so there's no need for you to do anything special, and this cannot possibly fail. There is no ambiguity.

Downcasting

What if we want to do the reverse? Say we have a function that takes a NodeClass object as a parameter, and we'd like to treat it specially if it's a NameListClass object? Here's the answer:

void foo(NodeClass node) {
  NameListClass listRef = NameListClass::TypeCast(node);
  if (listRef) {
    // ...
  }
}

As it turns out, every class in EnScript has a static function named TypeCast(). TypeCast() takes an ObjectClass reference and returns a reference to an object for its particular type, e.g., NameListClass::TypeCast() returns a NameListClass object reference. Because every object inherits from ObjectClass implicitly (even NodeClass), you can pass just about anything into TypeCast(), and because NameListClass::TypeCast() returns a reference to a NameListClass object, EnScript's compiler doesn't complain about assigning the result to the listRef reference here. TypeCast() lets us bridge the gap, allowing us to treat our NodeClass as a NameListClass.

Note here that we then check listRef to see whether its null. Why? Well, what if someone called foo() and passed in an EntryClass object? An EntryClass object is not a NameListClass object, so NameListClass::TypeCast() will return null. By doing so, you can safely test your downcast. Of course, if you didn't do the check, the code would compile but you'd end up with a null reference error when someone called foo() with anything other than a NameListClass object.

One more thing about TypeCast(): Don't pass it a null reference. TypeCast() itself will generate a null reference error if you pass it null. Therefore, to be truly safe, foo() should look like this:

void foo(NodeClass node) {
  if (node) {
    NameListClass listRef = NameListClass::TypeCast(node);
    if (listRef) {
      // ...
    }
  }
}

Single Object Inheritance is the Root of All Evil

If you make your own class hierarchies, you will find yourself having to make use of TypeCast() quite a bit. Let's say you have an AnimalClass as a base class and two derived classes, DogClass and CatClass. Further, we want to have a method to check whether two AnimalClass objects are equal (...uh, whatever that means...). So, we would have to create a virtual function in AnimalClass, isEqual(), that looks like this:

class AnimalClass {
  pure bool isEqual(AnimalClass other);
}

and then override the function in DogClass and CatClass. Clearly, a dog is not ever going to be equal to a cat. But not all cats are equal to each other, either. We will need to check their favorite brand of kitty food, kitty litter, markings, meowing habits, and whatnot to decide two cats are equal. So, even though isEqual() receives an AnimalClass object, we'll need to treat it as a CatClass to inspect all the member variables that are specific to cats and make this determination.

class CatClass {
  String FaveWetFood, FaveDryFood, FaveLitter;

  virtual bool isEqual(AnimalClass other) {
    CatClass cat = CatClass::TypeCast(other);
    if (cat) {
      return FaveWetFood == cat.FaveWetFood
        && FaveDryFood == cat.FaveDryFood
        && FaveLitter == cat.FaveLitter;
    }
    return false; // must be a dog or a ferret
  }
}

The TypeCast() ends up being necessary because there's something common we want to do for all animals (e.g., make equality comparisons), so the functionality must go in the base class and the function signature must involve an AnimalClass object. In languages like Java, SmallTalk, and EnScript, programmers end up having to do this a lot.

In C++, which has templates, you can often make use of the Curiously Recurring Template Pattern.

2010-07-26

Rob Pike on a False Dichotomy

Rob Pike's keynote at OSCON, describing what I think is a very valid niche: the need for a modern statically typed programming language. Of course, Go is his answer, but at 8:55 in, he makes the case that folks have been set up to adopt a false dichotomy, that static typing is necessarily bad and dynamic typing is necessarily good.

2010-07-09

autocrap tools

Love it. Anyone ever notice that they're slow, too? In 2010, why does it take 30s to scan my system for the same old crap, every single time?

2010-07-01

looping

I want a loop construct like Python's join(). That is:


Iterator cur = start;
if (cur != end) {
  foo(*cur);
  while (++cur != end) {
    bar(*cur);
    foo(*cur);
  }
}

2010-06-04

Don't forget "inline" and "register"

The conventional wisdom these days is that compilers are, like the Great and Powerful Oz, all-knowing, all-powerful, and all-optimizing. Instead of foolishly trying to make your code faster, you, mere mortal developer, should simply choose good algorithms, use standard libraries, and specify "-O3" to gcc.


The other day, on a lark, I decided to declare one of my functions "inline", since it was used in an inner loop of my main routine, and I also prefixed some of its local variables with the "register" keyword (I made these edits separately, so I could see whether either made a difference). My thought was, well, the compiler's probably chopping this code up six ways past Sunday, but, what the hell?, can't hurt to try. In return for the 5 minutes of time I spent doing this (including compiling and testing), I received a 20% performance improvement.

I'd already been specifying "-O3". I'd removed all the heap allocations. The code was fast enough that Shark couldn't find any obvious hot-spots. 20%.

So, after you write well-factored code, and choose good algorithms, and use profiling to find the hot-spots, and wonder where the next improvement will come from... remember your friends, "inline" and "register" and take 5 minutes to see whether they improve things for you.

2010-05-10

Damian Conway's Modest Proposal

http://www.csse.monash.edu.au/~damian/papers/HTML/ModestProposal.html

A proposal to create a new language on top of C++. Unfortunately, it's co-authored by a noted Perltard, and some of the operators are... icky.

They lost me at "+:=". Also, the argument of mathematical consistency for using "=" instead of "==" is bone-headed. Maybe it's that Dave Mark was especially skilled at explaining C when I taught myself (16 years ago...), but I have never, ever been confused by "==". Since the only way their proposal could meet with success is if it was embraced by existing C++ programmers, they should have left the operators alone.

2010-05-04

Xcode with SCons

I'm not a big fan of using IDEs for project builds, but IDEs excel at source-level debugging. The scons wiki has information on how to use Apple's Xcode with scons. This works great.

I tend to agree with folks who advocate unit testing and print/log debugging over stepping through code. I've seen my share of debugger-obsessed developers (to the point of writing custom plugins for Visual Studio) who could have spent a lot less time in the debugger if they'd spent more time writing clean code with TDD. However, I've seen the opposite sort of developer: writing test after test without solving the bug. If you can narrow down where the bug is occurring, stepping through code, line by line, will often reward the patient programmer and quickly rule out the wild, paranoid theories about compiler bugs that everyone develops in the absence of hard evidence.

2010-04-28

Annoyed with C++

The bloom is off the rose. I've been loyal; Lord knows, it hasn't been easy sticking with C++ in the face of all the dynamic language bigotry (seriously, guys, I understand your slow-ass languages have their place, just don't give me a hard time about me wanting my own programs to be fast, m'kay?).

But. This is asinine:

#include

// somewhere in a function

size_t aNumber = 5; // perfectly fine
...
return std::min(1, aNumber); // compile-error

The reason this is giving me a compile error is because I'm passing mismatched types to std::min and it can't figure out the type deduction. That is to say, it thinks that "1" is an int, and only an int. The variable, on the other hand, is not an int, it's a "size_t" (AKA "unsigned long", varying from an int in size and sign). However, when the type has been declared, the compiler has no problem assigning a constant to the size_t.

The solution?

return std::min(1ul, aNumber);

Lame. I don't think you needed to do this back in the day, when compilers were worse. Of course, you did have to make the call to std::min(1, aNumber). Progress...

2010-03-27

Would Descartes have programmed in Pascal?

My man Umberto on Catholicism and the Cult of the Mac. What's fantastic is that this was written before the return of the Messiah. Can you imagine what he could write now if he chose to? iPhones as rosary beads? Vista as the decline of mainline denominations? malware and exorcism?

2010-02-25

Kate

An article about a text editor for linux, named Kate, popped up at me today. I'd never heard of it before, and being pretty unhappy with the usual assortment of Linux-based text editors, I was initially interested. Maybe I'll fire up my Ubuntu partition and give it a try. My reaction to the screenshots, though, is "meh."

A bit of hilarity from the article:
Having a spell checker built into a text editor is a rarity.
Really?

All the text editors I use on a regular basis have spell-check. Moreover, I can't remember the last time I used a text editor that didn't have spell-check.

I think my ideal text editor would be a combination of TextMate and Acme, with the proviso that copy and paste had better be mapped to ⌘-C and ⌘-V, because that's what I learned when I was 10. Cx-y, for "yank," makes no sense to anyone who grew up in the '80s. I really want contextual side windows that show me other occurrences of highlighted tokens, documentation, etc. It should be automatic, and know that I have a big monitor and therefore do not want my focus replaced to save screen space.

2010-02-14

Do you want to spend the rest of your life selling text ads, or do you want a chance to change the world?

Google Buzz was the topic of the week. I woke up on Wednesday, logged into Gmail, and there was Buzz, with a message from my Googler buddy, Gavin. I tried replying with a comment to his Buzz, and it made me realize I had something called a "Google Profile" and that I had to make it public to comment. Taking a look at what was in my profile, I decided commenting wasn't worth exposing my profile. By Wednesday evening, a lot of other folks had figured this out, too.

Buzz and its privacy settings are not what this post is about, though. Google was forefront in my mind this week. I use Google services for everything: searching, navigating, reading, writing, calculating, emailing, chatting, blogging, scheduling, and organizing. For this, I pay nothing. I only trade a bit of my privacy. Frankly, it's a fair trade. As long as I can keep the big G from exposing my contacts to each other, I'm happy. I probably spent 50-60 hours talking to Google servers this week, maybe 70-80.

What pays the bills at the GooglePlex are those funny yellow text ads you see in search results. Ever click on them? Me neither. I guess some people do, though. When they do, the advertiser gets charged a penny. Get enough monkeys clicking on enough links to build up enough pennies, and you get a wildly profitable public company that can lash together dozens of data centers into the world's largest supercomputer and hire the world's most effective computer scientists (the ones who peg the needle on "smart, gets things done").

The thing I noticed this past week, though, is that all these Google services I use are... beta, or, maybe 1.0. Google Docs table of contents don't let you exclude sections that come before the TOC, footnotes can't be styled separately from your body text, your spreadsheets must be organized just-so in order to get useful labels in charts and graphs, Buzz annoyingly makes my lizard brain click on it just because some person I don't know has commented on a buzz I haven't commented on (even Facebook isn't this demanding). Still, these are extremely useful products. I groan every time I have to fire up Word or Excel on my Mac.

If I were an engineer working on these products, I'd feel satisfied, mostly. I'd be providing pretty good products to people, and doing so in an aesthetically pleasing way, technologically, all HTML/CSS/JavaScript goodness, no win32 calls in sight. But, and it's probably just the way I'm wired, I'd feel a bit unfulfilled. I'd look at my paycheck, laptop, cube, sushi-filled cafeteria, massage chair, cooler filled with those $5 Naked fruit juice bottles, copper-plated espresso machine, and wifi-equipped commuter van, and I'd know that I hadn't paid for them. The smart folks working on text ads had. They're the major leaguers in the organization.

And then I'd think, huh, I don't want to be them, either, even if I were that smart. Sure, I'd get to work for Peter Norvig and Udi Manber, and they'd be nice about how they were a thousand times smarter than me and they'd still offer to fill my brain as much as possible. They'd let me run my Sawzall programs on a thousand-node cluster. I'd get to ask Rob Pike a question at a tech talk. That'd be sweet. But despite all these powerful lures... I'd be selling text ads to monkeys.

It could very well be that I'm rationalizing, and that I'd be happy as a clam if I could make it through the seven walls of Google's HR process (talk about defense-in-depth). I dunno. It's worth thinking about.

2010-01-28

Dear Larry Ellison,

It has come to my attention that James Gosling now works for you. It is my recommendation that you remedy this situation immediately. As justification I present the following Java snippet:

Object obj = new Object(); 
// ...
if (obj != null) {

}


Do you really want someone working for you at Oracle who designs a language where you must use a binary operator to check whether a reference is null? Consider the obvious alternative:

Object obj = new Object();
//...
if (obj) {

}

Don't you agree that this is more Zen-like? Me, too. I'm so glad you're in charge now.

That is all.

Best wishes,

Jon

2010-01-18

Java

I hates it.

2010-01-15

git rebase

A very nice introduction to git rebase:

http://book.git-scm.com/4_rebasing.html

Down the rabbit hole

Show me a developer who never goes down the rabbit hole, and I'll show you a quiche-eater. Yes, it's important to be disciplined and not get constantly lost in depth-first-search coding, but... it's also important to have fun from time-to-time.

One thing I love about git is its support for going down the rabbit hole. You're working on trunk, having fun, writing up a mess of code, and then you realize, gee, it would be Good to checkin, and start climbing out of the hole... but it would be Bad to commit to my trunk, and my working directory is dirty. git stash to the rescue:

cat > ~/bin/rabbit_hole.sh
git stash
git checkout -b $1
git stash apply
^d

git stash essentially creates a private, temporary branch of your dirty working directory, and then resets your working directory. Once this is done, you can create a new branch that's clean, and then use git stash to apply your changes back to the working directory. Now you can commit to the side-branch and work your way out of the hole.

The one danger with git stash that I've run into is if you do not apply your changes, you can build up a long stack of old crap. So, don't do that.

2010-01-09

If worse is better, does that mean awful is best?

A few more thoughts on Alex's post, which is still reverberating around the blogosphere.
  1. View source was necessary, but not sufficient. Simplicity, in the form of a single level of abstraction, was also necessary. JavaScript, CSS, namespaces, microformats and whatnot add indirection and abstraction, and hence power, but... being confronted with multiple concerns from the get-go is not a good way to learn something new.
  2. HTML5 removes support for some old, old, old tags, and instead requires folks to use CSS for styling their web pages. CSS is undoubtedly the right way to format html. But for Microsoft Word users, maybe 5% of whom know how to use style sheets, will requiring the use of a whole 'nother standard—and a level of indirection—just to italicize or embolden a word be a barrier to entry? Um, yes.
  3. To the casual observer, HTML5 appears to be what we here in our nation's capital like to call a Charlie Foxtrot. Will all the contention and confusion hinder adoption? Um, yes.
  4. There's probably a reason why php is so popular, even if it is incredibly awful. Just like how BASIC, with its GOTOs, was popular.
There is a virtue in directness, and it is a low barrier to entry for newbs. Yes, it leads to awful source. That's why the narrative of every decent beginner's programming book is essentially a gradual increase in the use and understanding of abstraction. When folks have a dozen <font> tags they have to change, that's when you introduce CSS.