Dr. Dobb's Journal - January 2008 - (Page 68) d02sutt_p5ma 11/9/07 11:42 AM Page 68 Instantly Search Terabytes of Text N N over two dozen indexed, unindexed, fielded data and full-text search options highlights hits in HTML, XML and PDF, while displaying links, formatting and images converts other file types (word processor, database, spreadsheet, email and attachments, ZIP, Unicode, etc.) to HTML for display with highlighted hits Spider supports static and dynamic Web content, with WYSWYG hit-highlighting API supports .NET/.NET 2.0, C++, Java, SQL databases. New .NET/.NET 2.0 Spider API Figure 1: Sample module/layer decomposition. N N N Spider ($199) Desktop with $800) h Spider (from Network wit 99) ider (from $9 Web with Sp om $2,500) CD/DVDs (fr Publish for New beta Win & .NET 64-bit Engine for Linux Engine for dtSearch® Reviews N “Bottom line: dtSearch manages a terabyte N N N N N of text in a single index and returns results in less than a second” – InfoWorld “For combing through large amounts of data, dtSearch “leads the market” – Network Computing “Blindingly fast”– Computer Forensics: Incident Response Essentials “Covers all data sources powerful Webbased engines”– eWEEK “Searches at blazing speeds”– Computer Reseller News Test Center “The most powerful document search tool on the market”– Wired Magazine Software can’t always be perfectly layered, but exceptions should be rare. After all, if you can’t define such layers, it means that there is a cycle among the modules somewhere that includes code in what should be a lower level subsystem calling into higher level code somewhere, such as via a callback, and you have the potential for reentrancy even in single-threaded code. And remember, reentrancy is a form of concurrency, so the program can observe corrupt state even in singlethreaded code. If higher level code is in the middle of taking the system from one valid state to another, thus temporarily breaking some invariant, and calls into lower level code, the trouble is that if that call could ultimately call back into the higher level code it might see the broken invariant. Layering helps to solve this single-threaded concurrency problem for the same reasons it helps to solve the more general multithreaded version. • Write a wrapper around each of your favorite language- or platform-specific mutex types, and let the wrapper’s constructor(s) take a level number parameter that it saves in a myLevel member. Use these wrappers everywhere. (Where practical, save time by making the wrapper generic—as a C++ template, or a Java or .NET generic—so that it can be instantiated to wrap arbitrary mutex types that have similar lock/unlock features. You might only have to write it once.) • Give the wrapper class a thread-local static variable called currentLevel, initialized to a value higher than any valid lock level. • In the wrapper’s lock method (or similar), assert that currentLevel is greater than myLevel, the level of the mutex that you’re about to try to acquire. Remember, if the previous value of currentLevel is using another member variable, then set currentLevel = myLevel; and acquire the lock. • In the wrapper’s unlock method (or similar), restore the previous value of currentLevel. • As needed, also wrap other necessary methods you need to be able to use, such as try_lock. Any of these methods that might try to acquire the lock should do the same things as lock does. • Finally, write a “lock-multiple” method lock( m1, m2, … ) that takes a variable number of lockable objects, asserts that they are all at the same level, and locks them in their address order (or their GUID order, or some other globally consistent order). The reason for using assertions in the lock methods is so that, in a debug build, we force any errors to be exposed the first time we execute the code path that violates the lock hierarchy rules. That way, we can expect to find violations at test time and have high confidence that the program is deadlock-free based on code path coverage. Enabling such deterministic test-time failures is a great improvement over the way concurrency errors usually manifest, namely as nondeterministic runtime failures that can’t be thoroughly tested using code path coverage alone. But often our test-time code path coverage isn’t complete, either because it’s impossible to cover all possible code path combinations or because we might forget a few cases; so prefer to also perform the tests in release builds, recording violations in a log or For hundreds more reviews — and developer case studies — see www.dtsearch.com Contact dtSearch for fully-functional evaluations The Smart Choice for Text Retrieval® since 1991 1-800-IT-FINDS www.dtsearch.com 68 Dr. Dobb’s Journal l www.ddj.com l January 2008 ® Frameworks and Lock Hierarchies It is a curious thing that major frameworks that supply mutexes and locks do nothing to offer any direct support for lock hierarchies. Everyone is taught that lock hierarchies are a best practice, but then are generally told to go roll their own. The frameworks vendors will undoubtedly fix this little embarrassment in the future, but for now, here’s a useful recipe to follow as you do roll your own level-aware mutex wrapper. You can adapt this simple sketch to your project’s specific needs (for example, to suit details such as whether your lock operation is a method or a separate class): http://www.dtsearch.com http://www.dtsearch.com http://www.dtsearch.com http://www.ddj.com
For optimal viewing of this digital publication, please enable JavaScript and then refresh the page. If you would like to try to load the digital publication without using Flash Player detection, please click here.