Created
June 6, 2019 15:29
-
-
Save r13l/2911f93cbe66fb4ed50f9d9eb1eb252e to your computer and use it in GitHub Desktop.
Primitive condition system in Go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"log" | |
) | |
type handler func(ctx context.Context, err error) | |
type handlerRecord struct { | |
handler handler | |
prev *handlerRecord | |
} | |
func (h handlerRecord) signal(ctx context.Context, err error) { | |
if h.handler != nil { | |
h.handler(ctx, err) | |
} | |
if h.prev != nil { | |
h.prev.signal(ctx, err) | |
} | |
} | |
type key int | |
const handlerKey key = 0 | |
func withHandler(ctx context.Context, h handler) context.Context { | |
record, _ := ctx.Value(handlerKey).(*handlerRecord) | |
return context.WithValue(ctx, handlerKey, &handlerRecord{h, record}) | |
} | |
func signal(ctx context.Context, err error) { | |
record, ok := ctx.Value(handlerKey).(*handlerRecord) | |
if ok { | |
record.signal(ctx, err) | |
} | |
panic(fmt.Sprintln("error signaled but not handled:", err)) | |
} | |
type restart func(context.Context, []interface{}) | |
type restartRecord struct { | |
name string | |
restart restart | |
prev *restartRecord | |
} | |
func (r restartRecord) invoke(ctx context.Context, name string, args ...interface{}) { | |
if name == r.name && r.restart != nil { | |
r.restart(ctx, args) | |
} | |
if r.prev != nil { | |
r.prev.invoke(ctx, name, args...) | |
} | |
} | |
const restartKey key = 1 | |
func runWithRestart(ctx context.Context, name string, r restart, f func(context.Context)) { | |
v := new(int) | |
defer func() { | |
p := recover() | |
if p != nil && p != v { | |
panic(p) | |
} | |
}() | |
rr := func(ctx context.Context, args []interface{}) { | |
r(ctx, args) | |
panic(v) | |
} | |
record, _ := ctx.Value(restartKey).(*restartRecord) | |
ctx = context.WithValue(ctx, restartKey, &restartRecord{name, rr, record}) | |
f(ctx) | |
} | |
func invokeRestart(ctx context.Context, name string, args ...interface{}) { | |
record, ok := ctx.Value(restartKey).(*restartRecord) | |
if ok { | |
record.invoke(ctx, name, args) | |
} | |
} | |
func main() { | |
ctx := withHandler(context.Background(), func(ctx context.Context, err error) { | |
invokeRestart(ctx, "abort", err) | |
}) | |
var v int | |
// this will succeed, because the error is handled and restarted | |
runWithRestart(ctx, "abort", func(ctx context.Context, args []interface{}) { | |
v = 2 | |
log.Println("aborted:", args) | |
}, func(ctx context.Context) { | |
foo(ctx) | |
}) | |
log.Println(v) // note that abort was called | |
// this will succeed, because no error is signaled | |
runWithRestart(ctx, "abort", func(ctx context.Context, args []interface{}) { | |
v = 3 | |
log.Println("aborted:", args) | |
}, func(ctx context.Context) { | |
bar(ctx) | |
}) | |
log.Println(v) // note that the abort restart was never called | |
// this will crash because the error is not handled | |
runWithRestart(ctx, "some-other-handler", func(ctx context.Context, args []interface{}) { | |
log.Println("handled some other way:", args) | |
}, func(ctx context.Context) { | |
foo(ctx) | |
}) | |
} | |
func foo(ctx context.Context) { | |
signal(ctx, errors.New("some bad error")) | |
} | |
func bar(ctx context.Context) { | |
// note no signal | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment