forked from gohugoio/hugo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable hugo server SIGINT after loading bad config
If a user starts hugo server and writes to the config file in a way that renders it invalid, hugo server stops responding to SIGINTs, meaning that a user needs to send a SIGKILL to the hugo process to stop it. *commandeer.serve needs to obtain a *hugolib.HugoSites in order to close the Hugo environment. After handling signals/the stop channel, *commandeer.serve calls c.hugo() to get the *hugolib.HugoSites. This function receives from the c.created channel, which is closed when the HugoSites is ready. However, if an error took place while loading the config, c.created would never be closed, so Hugo would block on this channel even after handling SIGINT. The solution is to close c.created if we failed to load a config when rebuilding the site, then check at the end of *commandeer.serve whether the *hugolib.HugoSites is nil. If it is, we stop waiting for a HugoSites to close, and exit the hugo process. One issue that resulted from this fix was how to reset the c.created channel during site rebuilds. In the current code, this is done by assigning c.commandeerHugoState to a newCommandeerHugoState in *commandeer.fullRebuild. However, this creates a race condition, since other goroutines can be reading the value of c.created just as *commandeer.fullRebuild is attempting to reassign it. This change fixes the race condition by adding the lazy.Notifier type, which preserves the use of c.created while allowing for it to be reset in a goroutine-safe way. c.created is now a lazy.Notifier. I added this type to the lazy package because it didn't seem worth it to add a new package, but we can do this if it's cleaner. (I was thinking about using sync.Cond for this, but the approach I used is closer to the original use of the c.created channel and changes less code.) Also adds an integration test for interrupting the server after fixing an invalid configuration. An earlier version of this change caused hugo server to stop exiting with a SIGINT after fixing a bad config, so I wanted to ensure that we can prevent this. Fixes gohugoio#8340
- Loading branch information
Showing
6 changed files
with
340 additions
and
8 deletions.
There are no files selected for viewing
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright 2019 The Hugo Authors. All rights reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package lazy | ||
|
||
import ( | ||
"runtime/debug" | ||
"strings" | ||
"sync" | ||
) | ||
|
||
// Notifier as a synchronization tool that is queried for just-in-time access | ||
// to a resource. Callers use Wait to block until the resource is ready, and | ||
// call Close to indicate that the resource is ready. Reset returns the | ||
// resource to its locked state. | ||
// | ||
// Notifier must be initialized by calling NewNotifier. | ||
type Notifier struct { | ||
// Channel to close when the protected resource is ready. This must only | ||
// be accessed via calling the currentCh method to avoid race conditions. | ||
ch chan struct{} | ||
// For locking the channel while resetting it | ||
mu *sync.RWMutex | ||
} | ||
|
||
// NewNotifier creates a Notifier with all synchronization mechanisms | ||
// initialized. | ||
func NewNotifier() *Notifier { | ||
return &Notifier{ | ||
ch: make(chan struct{}), | ||
mu: &sync.RWMutex{}, | ||
} | ||
} | ||
|
||
// isClosed checks whether a channel is closed. If calling from a Notifier | ||
// method, the calling goroutine must hold and release the Notifier.mu lock. | ||
func isClosed(ch chan struct{}) bool { | ||
select { | ||
// Already closed | ||
case <-ch: | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
// currentCh safely returns the current channel. Because this locks and unlocks | ||
// the mutex, callers must not perform any other locking until the channel is | ||
// returned. | ||
func (n *Notifier) currentCh() chan struct{} { | ||
n.mu.RLock() | ||
defer n.mu.RUnlock() | ||
return n.ch | ||
} | ||
|
||
// Wait waits for the Notifier to be ready, i.e., for Close to be called | ||
// somewhere | ||
func (n *Notifier) Wait() { | ||
ch := n.currentCh() | ||
<-ch | ||
s := string(debug.Stack()) | ||
if strings.Contains(s, "newWatcher") { | ||
} | ||
return | ||
} | ||
|
||
// Close unblocks any goroutines that called Wait | ||
func (n *Notifier) Close() { | ||
ch := n.currentCh() | ||
n.mu.Lock() | ||
defer n.mu.Unlock() | ||
if !isClosed(ch) { | ||
close(ch) | ||
} | ||
return | ||
} | ||
|
||
// Reset returns the resource to its pre-ready state while locking | ||
func (n *Notifier) Reset() { | ||
ch := n.currentCh() | ||
n.mu.Lock() | ||
// No need to reset since the channel is open | ||
if !isClosed(ch) { | ||
return | ||
} | ||
defer n.mu.Unlock() | ||
n.ch = make(chan struct{}) | ||
return | ||
} |
Oops, something went wrong.