mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-18 06:29:42 +02:00
fixed tcp/udp I/O, deadlock, nil dereference; improved docker watcher, idlewatcher, loading page
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
package idlewatcher
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -16,33 +14,45 @@ import (
|
||||
P "github.com/yusing/go-proxy/proxy"
|
||||
PT "github.com/yusing/go-proxy/proxy/fields"
|
||||
W "github.com/yusing/go-proxy/watcher"
|
||||
event "github.com/yusing/go-proxy/watcher/events"
|
||||
)
|
||||
|
||||
type watcher struct {
|
||||
*P.ReverseProxyEntry
|
||||
|
||||
client D.Client
|
||||
|
||||
refCount atomic.Int32
|
||||
|
||||
stopByMethod StopCallback
|
||||
wakeCh chan struct{}
|
||||
wakeDone chan E.NestedError
|
||||
running atomic.Bool
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
l logrus.FieldLogger
|
||||
}
|
||||
|
||||
type (
|
||||
watcher struct {
|
||||
*P.ReverseProxyEntry
|
||||
|
||||
client D.Client
|
||||
|
||||
ready atomic.Bool // whether the site is ready to accept connection
|
||||
stopByMethod StopCallback // send a docker command w.r.t. `stop_method`
|
||||
|
||||
wakeCh chan struct{}
|
||||
wakeDone chan E.NestedError
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
refCount *sync.WaitGroup
|
||||
|
||||
l logrus.FieldLogger
|
||||
}
|
||||
|
||||
WakeDone <-chan error
|
||||
WakeFunc func() WakeDone
|
||||
StopCallback func() E.NestedError
|
||||
)
|
||||
|
||||
var (
|
||||
mainLoopCtx context.Context
|
||||
mainLoopCancel context.CancelFunc
|
||||
mainLoopWg sync.WaitGroup
|
||||
|
||||
watcherMap = make(map[string]*watcher)
|
||||
watcherMapMu sync.Mutex
|
||||
|
||||
newWatcherCh = make(chan *watcher)
|
||||
|
||||
logger = logrus.WithField("module", "idle_watcher")
|
||||
)
|
||||
|
||||
func Register(entry *P.ReverseProxyEntry) (*watcher, E.NestedError) {
|
||||
failure := E.Failure("idle_watcher register")
|
||||
|
||||
@@ -67,12 +77,12 @@ func Register(entry *P.ReverseProxyEntry) (*watcher, E.NestedError) {
|
||||
w := &watcher{
|
||||
ReverseProxyEntry: entry,
|
||||
client: client,
|
||||
refCount: &sync.WaitGroup{},
|
||||
wakeCh: make(chan struct{}, 1),
|
||||
wakeDone: make(chan E.NestedError, 1),
|
||||
l: logger.WithField("container", entry.ContainerName),
|
||||
}
|
||||
w.refCount.Add(1)
|
||||
w.running.Store(entry.ContainerRunning)
|
||||
w.stopByMethod = w.getStopCallback()
|
||||
|
||||
watcherMap[w.ContainerName] = w
|
||||
@@ -84,20 +94,9 @@ func Register(entry *P.ReverseProxyEntry) (*watcher, E.NestedError) {
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// If the container is not registered, this is no-op
|
||||
func Unregister(containerName string) {
|
||||
watcherMapMu.Lock()
|
||||
defer watcherMapMu.Unlock()
|
||||
|
||||
if w, ok := watcherMap[containerName]; ok {
|
||||
if w.refCount.Add(-1) > 0 {
|
||||
return
|
||||
}
|
||||
if w.cancel != nil {
|
||||
w.cancel()
|
||||
}
|
||||
w.client.Close()
|
||||
delete(watcherMap, containerName)
|
||||
w.refCount.Add(-1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +106,6 @@ func Start() {
|
||||
|
||||
mainLoopCtx, mainLoopCancel = context.WithCancel(context.Background())
|
||||
|
||||
defer mainLoopWg.Wait()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mainLoopCtx.Done():
|
||||
@@ -117,8 +114,11 @@ func Start() {
|
||||
w.l.Debug("registered")
|
||||
mainLoopWg.Add(1)
|
||||
go func() {
|
||||
w.watch()
|
||||
Unregister(w.ContainerName)
|
||||
w.watchUntilCancel()
|
||||
w.refCount.Wait() // wait for 0 ref count
|
||||
|
||||
w.client.Close()
|
||||
delete(watcherMap, w.ContainerName)
|
||||
w.l.Debug("unregistered")
|
||||
mainLoopWg.Done()
|
||||
}()
|
||||
@@ -137,31 +137,6 @@ func (w *watcher) PatchRoundTripper(rtp http.RoundTripper) roundTripper {
|
||||
}}
|
||||
}
|
||||
|
||||
func (w *watcher) roundTrip(origRoundTrip roundTripFunc, req *http.Request) (*http.Response, error) {
|
||||
w.wakeCh <- struct{}{}
|
||||
|
||||
if w.running.Load() {
|
||||
return origRoundTrip(req)
|
||||
}
|
||||
timeout := time.After(w.WakeTimeout)
|
||||
|
||||
for {
|
||||
if w.running.Load() {
|
||||
return origRoundTrip(req)
|
||||
}
|
||||
select {
|
||||
case <-req.Context().Done():
|
||||
return nil, req.Context().Err()
|
||||
case err := <-w.wakeDone:
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
case <-timeout:
|
||||
return getLoadingResponse(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) containerStop() error {
|
||||
return w.client.ContainerStop(w.ctx, w.ContainerName, container.StopOptions{
|
||||
Signal: string(w.StopSignal),
|
||||
@@ -205,7 +180,6 @@ func (w *watcher) wakeIfStopped() E.NestedError {
|
||||
case "paused":
|
||||
return E.From(w.containerUnpause())
|
||||
case "running":
|
||||
w.running.Store(true)
|
||||
return nil
|
||||
default:
|
||||
return E.Unexpected("container state", status)
|
||||
@@ -236,15 +210,12 @@ func (w *watcher) getStopCallback() StopCallback {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) watch() {
|
||||
watcherCtx, watcherCancel := context.WithCancel(context.Background())
|
||||
w.ctx = watcherCtx
|
||||
w.cancel = watcherCancel
|
||||
|
||||
dockerWatcher := W.NewDockerWatcherWithClient(w.client)
|
||||
|
||||
func (w *watcher) watchUntilCancel() {
|
||||
defer close(w.wakeCh)
|
||||
|
||||
w.ctx, w.cancel = context.WithCancel(context.Background())
|
||||
|
||||
dockerWatcher := W.NewDockerWatcherWithClient(w.client)
|
||||
dockerEventCh, dockerEventErrCh := dockerWatcher.EventsWithOptions(w.ctx, W.DockerListOptions{
|
||||
Filters: W.NewDockerFilter(
|
||||
W.DockerFilterContainer,
|
||||
@@ -265,7 +236,7 @@ func (w *watcher) watch() {
|
||||
select {
|
||||
case <-mainLoopCtx.Done():
|
||||
w.cancel()
|
||||
case <-watcherCtx.Done():
|
||||
case <-w.ctx.Done():
|
||||
w.l.Debug("stopped")
|
||||
return
|
||||
case err := <-dockerEventErrCh:
|
||||
@@ -273,16 +244,18 @@ func (w *watcher) watch() {
|
||||
w.l.Error(E.FailWith("docker watcher", err))
|
||||
}
|
||||
case e := <-dockerEventCh:
|
||||
switch e.Action {
|
||||
case event.ActionDockerStartUnpause:
|
||||
w.running.Store(true)
|
||||
w.l.Infof("%s %s", e.ActorName, e.Action)
|
||||
case event.ActionDockerStopPause:
|
||||
w.running.Store(false)
|
||||
w.l.Infof("%s %s", e.ActorName, e.Action)
|
||||
switch {
|
||||
// create / start / unpause
|
||||
case e.Action.IsContainerWake():
|
||||
ticker.Reset(w.IdleTimeout)
|
||||
w.l.Info(e)
|
||||
default: // stop / pause / kill
|
||||
ticker.Stop()
|
||||
w.ready.Store(false)
|
||||
w.l.Info(e)
|
||||
}
|
||||
case <-ticker.C:
|
||||
w.l.Debug("timeout")
|
||||
w.l.Debug("idle timeout")
|
||||
ticker.Stop()
|
||||
if err := w.stopByMethod(); err != nil && err.IsNot(context.Canceled) {
|
||||
w.l.Error(E.FailWith("stop", err).Extraf("stop method: %s", w.StopMethod))
|
||||
@@ -301,57 +274,3 @@ func (w *watcher) watch() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getLoadingResponse() *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
Header: http.Header{
|
||||
"Content-Type": {"text/html"},
|
||||
"Cache-Control": {
|
||||
"no-cache",
|
||||
"no-store",
|
||||
"must-revalidate",
|
||||
},
|
||||
},
|
||||
Body: io.NopCloser(bytes.NewReader((loadingPage))),
|
||||
ContentLength: int64(len(loadingPage)),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
mainLoopCtx context.Context
|
||||
mainLoopCancel context.CancelFunc
|
||||
mainLoopWg sync.WaitGroup
|
||||
|
||||
watcherMap = make(map[string]*watcher)
|
||||
watcherMapMu sync.Mutex
|
||||
|
||||
newWatcherCh = make(chan *watcher)
|
||||
|
||||
logger = logrus.WithField("module", "idle_watcher")
|
||||
|
||||
loadingPage = []byte(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Loading...</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
setTimeout(function() {
|
||||
window.location.reload()
|
||||
}, 1000)
|
||||
// fetch(window.location.href)
|
||||
// .then(resp => resp.text())
|
||||
// .then(data => { document.body.innerHTML = data; })
|
||||
// .catch(err => { document.body.innerHTML = 'Error: ' + err; });
|
||||
};
|
||||
</script>
|
||||
<h1>Container is starting... Please wait</h1>
|
||||
</body>
|
||||
</html>
|
||||
`[1:])
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user