Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
language: go
go: ['1.16.x', '1.17.x', '1.18.x', '1.19.x']
go: ['1.18.x', '1.19.x']
go_import_path: github.com/teamwork/reload
jobs:
include:
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

Lightweight automatic reloading of Go processes.

After initialisation with `reload.Do()` any changes to the binary (and *only*
the binary) will restart the process. For example:
After initialisation with `reload.Do()` any changes to the binary will restart
the process. For example:

```go
func main() {
Expand Down Expand Up @@ -39,6 +39,9 @@ func main() {
}
```

This will run reloadTpl if any file in the "tpl" directory changes. The process
won't be restarted.

You can also use `reload.Exec()` to manually restart your process without
calling `reload.Do()`.

Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module github.com/teamwork/reload

go 1.16
go 1.18

require github.com/fsnotify/fsnotify v1.6.0

require golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
62 changes: 40 additions & 22 deletions reload.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
// Package reload offers lightweight automatic reloading of running processes.
//
// After initialisation with reload.Do() any changes to the binary will restart
// After initialisation with [reload.Do] any changes to the binary will restart
// the process.
//
// Example:
//
// go func() {
// err := reload.Do(log.Printf)
// if err != nil {
// panic(err)
// }
// }()
// go func() {
// err := reload.Do(log.Printf)
// if err != nil {
// panic(err)
// }
// }()
//
// The current process is replaced with [syscall.Exec]; this means that defered
// functions and signal handlers will not be run. You can use [OnExec] to run
// some code before the process is restarted.
//
// # Additional directories
//
// A list of additional directories to watch can be added:
//
// go func() {
// err := reload.Do(log.Printf, reload.Dir("tpl", reloadTpl)
// if err != nil {
// panic(err)
// }
// }()
// go func() {
// err := reload.Do(log.Printf, reload.Dir("tpl", reloadTpl)
// if err != nil {
// panic(err)
// }
// }()
//
// Note that this package won't prevent race conditions (e.g. when assigning to
// a global templates variable). You'll need to use sync.RWMutex yourself.
package reload // import "github.com/teamwork/reload"
// This will run reloadTpl if any file in the "tpl" directory changes. The
// process won't be restarted.
package reload

import (
"fmt"
Expand Down Expand Up @@ -64,7 +70,7 @@ func Dir(path string, cb func()) dir { return dir{path, cb} }
//
// The error return will only return initialisation errors. Once initialized it
// will use the log function to print errors, rather than return.
func Do(log func(string, ...interface{}), additional ...dir) error {
func Do(log func(string, ...any), additional ...dir) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return fmt.Errorf("reload.Do: cannot setup watcher: %w", err)
Expand Down Expand Up @@ -109,12 +115,17 @@ func Do(log func(string, ...interface{}), additional ...dir) error {
go func() {
for {
select {
case err := <-watcher.Errors:
if err != nil {
log("reload error: %v", err)
case err, ok := <-watcher.Errors:
if !ok {
return
}
case event := <-watcher.Events:
trigger := (event.Op&fsnotify.Write == fsnotify.Write) || (event.Op&fsnotify.Create == fsnotify.Create)
log("reload error: %v", err)
case event, ok := <-watcher.Events:
if !ok {
return
}

trigger := event.Has(fsnotify.Write) || event.Has(fsnotify.Create)
if !trigger {
continue
}
Expand Down Expand Up @@ -151,6 +162,9 @@ func Do(log func(string, ...interface{}), additional ...dir) error {
return nil
}

// OnExec is called before the current process is replaced.
var OnExec func()

// Exec replaces the current process with a new copy of itself.
func Exec() {
execName := binSelf
Expand All @@ -166,6 +180,10 @@ func Exec() {
closeWatcher()
}

if OnExec != nil {
OnExec()
}

err := syscall.Exec(execName, append([]string{execName}, os.Args[1:]...), os.Environ())
if err != nil {
panic(fmt.Sprintf("cannot restart: %v", err))
Expand Down