hot

package module
v0.0.0-...-70e0461 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 14, 2020 License: MIT Imports: 8 Imported by: 0

README

Hotreload

Ability to replace existing functions and methods on-the-fly for Go.

How it works

It's magic! Based on plugins and some intelligent (not really) on-the-fly code rewrite.

Dependencies

You must have go installed (obviously) and goimports needs to be reachable from your $PATH.

What kind of live code reload is supported?

It is only possible to live-reload code of existing functions and methods provided the following conditions are met:

  1. Functions can only use symbols that are declared in other packages. E.g. it's fine to call flag.Parse(), but not fine to accept a struct type that is defined in a current package (even if it is public).
  2. Methods must specify a public type as their receiver. The method itself can be private.
  3. Methods can only call public methods and access public fields of their receiver.

Usage

  1. Download version of go that supports plugins (1.8+ for Linux, 1.10+ for macOS, not yet supported on Windows)
  2. Run the example script in the separate console: ./cmd/live/run.sh. This will run an example web-server on port :8080 that has two web handlers: /get that returns counter value and /increment that increments a counter but does not return it.
  3. Try running $ curl 'http://127.0.0.1:8080/increment' to increment the in-memory counter.
  4. Try running $ curl 'http://127.0.0.1:8080/get' to get the value of the in-memory counter.
  5. Without closing the console with run.sh, edit the contents of cmd/live/subpkg/pkg.go and see what happens.
  6. After you see the line Hot reload was successful, try accessing /increment and /get again.
  7. If it worked, it's magic, right?

How to use for your own application

Please remember that the only purpose of this package is to demonstrate that live reloading is possible, and it's not advisable to run anything like this in production. The intention of this package is to speed up development of go code in some cases, especially for stateful applications.

So, in order to use the package in your development environment, you need to add the go hot.ReloaderLoop() somewhere close to the beginning in your main() function:

import hot "github.com/YuriyNasretdinov/hotreload"

func main() {
  go hot.ReloaderLoop()
  // the rest of your code
}

It will start reading new plugin names from stdin so you can't use stdin in your app for anything else.

After that, you need to write some wrapper script that would be close to ./cmd/live/run.sh:

#!/bin/sh
set -e
export GOPATH=$(go env GOPATH)

# Installing "hot" command line tool as a binary
go install github.com/YuriyNasretdinov/hotreload/cmd/hot

# Launching "hot" with two steps:
# 1. Compiling your program.
# 2. Launching it.

# You need to replace "<the directory with your application>" with a directory where changes
# will be  monitored.
# You can just set it to watch "$GOPATH/src" as a whole but it might fail to open
# so many directories at once.

# "<code to build your app>" is usually "go install <your_packages>"
# "<launch your app>" is usually "$GOPATH/bin/my_app".
# Note that the value of $GOPATH inside the shell command
# will be different from your normal GOPATH and
# it is important that you leave it in single quotes.

$GOPATH/bin/hot \
    -watch=$GOPATH/src/<the directory with your application> \
    sh -c 'set -e -x;\
    <code to build your app>;\
    <launch your app>'

Then, if you stick to the rules described below and only change the code of existing functions and methods, it should update code of your application on-the-fly! Note: Debuggers probably won't work well in conjuction with this implementation of hot code reload.

Can I use this in production?

Theoretically, yes! Hot code reload is based on https://github.com/YuriyNasretdinov/golang-soft-mocks which is memory- and thread-safe (which is not true for much more popular https://github.com/bouk/monkey). Provided you're fine with loading Go plugins on the fly in your production application and your changes are limited to what is described in "Examples of functions and methods that can be live-reloaded", it should be possible. It is probably not a good idea anyway because plugins cannot be unloaded from memory and if you live-reload your code in production too much, you will eventually run out of memory and waste a lot of resources.

Examples of functions and methods that can be live-reloaded

Function only accepts types from other packages

// good
func printTime(t time.Time) {
  log.Printf("Time: %s", t)
}

Function only accepts primitive types

// good
func add2(arg int64) int64 {
  return arg + 2
}

Function references only public variables from other packages

// good
func printOsArgs() {
  fmt.Printf("os.Args: %+v", os.Args)
}

Method only calls other public methods for the same public receiver

// good
func (e *Example) callOtherMethod() {
  e.OtherMethod()
}

Examples of functions and methods that cannot be live-reloaded

Function accepts types defined in the same package

// bad, can't accept types from the same package
func printMyOwnTime(t Time) {
  log.Printf("Time: %s", t)
}

Function accepts types defined in the same package

// bad, can't accept types from the same package
func incrementCounter(c *Counter) {
  (*c)++
}

Method calls private methods

// bad, can't call private methods
func (c *Counter) Increment() {
  c.doIncrement()
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CallOriginal

func CallOriginal(f interface{}, args ...interface{}) []interface{}

CallOriginal calls the original implementation of the function f. Note that the behaviour of recursive functions is not defined.

func CallOriginalByName

func CallOriginalByName(f string, args ...interface{}) []interface{}

CallOriginalByName calls the original implementation of the function f. Note that the behaviour of recursive functions is not defined.

func GetMockFor

func GetMockFor(f interface{}) interface{}

GetMockFor returns the mock that was set in Mock() method for the supplied function if such mock exists and nil otherwise.

func Mock

func Mock(src interface{}, dst interface{})

Mock substitutes the src function with dst in runtime. In order to pass function pointers to methods you need to write the following expression: `(*typeName).MethodName`.

func MockByName

func MockByName(src string, dst interface{})

MockByName mocks the function by it's name src. Dst is the function that should replace src.

The name of a function is formed using the following scheme:

  1. For plain functions it's just "package/Symbol", E.g. for `os.Open()` it would be "os/Open"
  2. For pointer methods it is "package/*receiverType.MethodName": E.g. for `Close` method of `*os.File` it would be "os/*File.Close"
  3. For value methods it is "package/receiverType.MethodName": E.g. for `IsZero` method of `time.Time` it would be "time/Time.IsZero"

func RegisterFunc

func RegisterFunc(fun interface{}, name string, p *int32)

RegisterFunc is a callback that is used in rewritten files to register the function so that it can be mocked. Do not use directly.

func ReloaderLoop

func ReloaderLoop()

ReloaderLoop starts a loop that loads new plugins and applies patches to existing functions. Suggested usage: `go hot.ReloaderLoop()`

func Reset

func Reset(f interface{})

Reset removes the mock that was set up for the function f, returning it to the original implementation. If there were no mocks set up for the function it is a noop.

func ResetAll

func ResetAll()

ResetAll removes the mocks that were set up for all functions.

func ResetByName

func ResetByName(src string)

ResetByName removes the mock that was set up for the function src, returning it to the original implementation. If there were no mocks set up for the function it is a noop.

Types

This section is empty.

Directories

Path Synopsis
cmd
hot

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL