Skip to content
A library for detecting certain improper uses of the "Defer, Panic, and Recover" pattern in Go programs
Go Python Shell Makefile
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
example
logo
scripts
.gitignore
LICENSE
Makefile
README.md
basic_test.go
nested_test.go
onedge_norace.go
onedge_race.go
test_util.go

README.md

OnEdge  

OnEdge is a library for detecting certain improper uses of the Defer, Panic, and Recover pattern in Go programs. OnEdge is lightweight in that it is easy to incorporate into your project and tries to stay out of your way as much as possible.

What sort of problems does OnEdge detect?

OnEdge detects global state changes that occur between (1) the entry point to a function that defers a call to recover and (2) the point at which recover is called. Often, such global state changes are unintentional, e.g., the programmer didn't realize that code executed before a panic could have a lasting effect on their program's behavior.

How does OnEdge work?

OnEdge reduces the problem of finding such global state changes to one of race detection. When the program enters a function that defers a call to recover, OnEdge launches a shadow thread. If that function later panics, then the function is re-executed in the shadow thread. If doing so causes the shadow thread to make a global state change before calling recover, then that change appears as a data race and can be reported by Go's race detector.

When Go's race detector is disabled, OnEdge does nothing.

Limitations

  1. OnEdge is a dynamic analysis, and like all dynamic analyses, its effectiveness depends upon the workload to which you subject your program. In other words, for OnEdge to detect some global state change, you must provide inputs to your program that cause it to make that global state change.

  2. For some programs, OnEdge's results are non-deterministic, i.e., OnEdge could report a global state change for some runs of the program, but not for others. We believe this is because ThreadSanitizer (on which Go's race detector is built) is itself non-deterministic.

  3. While nested uses to WrapFunc (see below) are supported, they can cause data races to be reported in OnEdge itself. This is because OnEdge must, e.g., keep track of shadow threads, and doing so involves modifying the global state. In theory, this problem could be solved by modifying the Go compiler (e.g., here) to ignore the OnEdge package. But modifying the Go compiler seems like a rather heavy handed solution to an infrequently occurring problem. So, for now, we recommend that users simply ignore any reported data races involving OnEdge's code itself.

  4. OnEdge is not currently thread safe. For now, you should not, e.g., call WrapFunc from two separate threads.

  5. If your program is multithreaded, then use of OnEdge may cause spurious data races to be reported. If you think that your program may contain a legitimate data race, then we recommend that you deal with that before enabling OnEdge. Further investigation into this issue is needed.

Incorporating OnEdge into your project

To incorporate OnEdge into your project, you must do three things:

  1. Wrap function bodies that defer calls to recover in onedge.WrapFunc(func() { ... }).

  2. Within those wrapped function bodies, wrap calls to recover in onedge.WrapRecover( ... ).

  3. Run your program with Go's race detector enabled, e.g., go run -race mysrc.go.

A function to which steps 1 and 2 have been applied might look something like this:

func handle(request Request) {
    onedge.WrapFunc(func() {
        defer func() {
            if r := onedge.WrapRecover(recover()); r != nil {
                log.Println(r)
            }
        }()
        ...
        // Panicky code that potentially modifies global state
        ...
    })
}

Step 3 will cause data races to be reported for global state changes that occur:

  • after entry to a function body wrapped by WrapFunc
  • but before a recover wrapped by WrapRecover.

An example can be found in the example subdirectory.

Note that while global state changes in the shadow thread are reported, they still occur. Be aware that if, say, those changes have external effects (e.g., a write to a database on an external machine), then those effects happen twice: once via the main thread and once via the shadow thread. (Of course, this is exactly the sort of problem that OnEdge is meant to detect.)

Testing OnEdge

OnEdge itself can be tested in two ways:

  • make basic_test performs a set of basic tests.
  • make nested_test tests nested uses of WrapFunc. This test is expensive as it performs a 2^22 exhaust. On a MacBook Pro, this test takes the better part of a work day to run.

Scripts

The scripts subdirectory contains some experimental scripts for filtering the Go race detector's output.

References

License

The code in this repository is licensed under the Apache 2.0 license.

You can’t perform that action at this time.