-
Notifications
You must be signed in to change notification settings - Fork 13
Release log streams before deleting resources #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
danegsta
wants to merge
6
commits into
main
Choose a base branch
from
fix-log-stream-file-release
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d94ca7a
Release log streams before deleting resources
danegsta b36c006
Fix log stream ID race
danegsta 71a1e2f
Release resource logs before test session cleanup
danegsta a7a46f5
Avoid lock contention during log stream release
danegsta 24f6167
Preserve delayed log stream shutdown on delete
danegsta 9e30759
Back out container soft-delete stream stop
danegsta File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import ( | |
| "io" | ||
| "os" | ||
| "sync" | ||
| "sync/atomic" | ||
|
|
||
| "github.com/go-logr/logr" | ||
| apiserver_resource "github.com/tilt-dev/tilt-apiserver/pkg/server/builder/resource" | ||
|
|
@@ -31,7 +32,7 @@ import ( | |
| ) | ||
|
|
||
| var ( | ||
| lastLogStreamID logs.LogStreamID // Used to generate unique log stream IDs for each log stream | ||
| lastLogStreamID atomic.Uint64 // Used to generate unique log stream IDs for each log stream | ||
| ) | ||
|
|
||
| type containerLogStreamer struct { | ||
|
|
@@ -50,6 +51,12 @@ type containerLogStreamer struct { | |
| lock *sync.Mutex | ||
| } | ||
|
|
||
| type containerStreamsToStop struct { | ||
| streamKind string | ||
| streams []*usvc_io.FollowWriter | ||
| cancelStreams func([]*usvc_io.FollowWriter) | ||
| } | ||
|
|
||
| func NewLogStreamer(log logr.Logger) *containerLogStreamer { | ||
| return &containerLogStreamer{ | ||
| startupLogStreams: make(logs.LogStreamMop), | ||
|
|
@@ -213,13 +220,12 @@ func (c *containerLogStreamer) StreamLogs( | |
| // to account for the fact that ContainerLogSource.CaptureContainerLogs() is an asynchronous operation. | ||
| // A few no-data stop retries will allow us to get log lines from a container that we just started capturing logs for, regardless of follow/non-follow mode. | ||
| const noDataStopRetries = 3 | ||
| followWriter := usvc_io.NewFollowWriter(ctx, src, dest, usvc_io.WithNoDataStopRetries(noDataStopRetries)) | ||
| followWriter := usvc_io.NewFollowWriter(ctx, src, dest, usvc_io.WithNoDataStopRetries(noDataStopRetries), usvc_io.WithCloseSourceOnCancel()) | ||
|
|
||
| c.lock.Lock() | ||
| defer c.lock.Unlock() | ||
|
|
||
| streamID := lastLogStreamID + 1 | ||
| lastLogStreamID = streamID | ||
| streamID := logs.LogStreamID(lastLogStreamID.Add(1)) | ||
|
|
||
| var streams logs.LogStreamMop | ||
| switch opts.Source { | ||
|
|
@@ -240,7 +246,6 @@ func (c *containerLogStreamer) StreamLogs( | |
|
|
||
| go func() { | ||
| defer cleanup() | ||
| defer src.Close() | ||
|
|
||
| streamLog.V(1).Info("Starting streaming logs to destination ...") | ||
| <-followWriter.Done() | ||
|
|
@@ -274,62 +279,91 @@ func (c *containerLogStreamer) OnResourceUpdated(evt apiv1.ResourceWatcherEvent, | |
| return | ||
| } | ||
|
|
||
| c.lock.Lock() | ||
| defer c.lock.Unlock() | ||
| var streamsToStop []containerStreamsToStop | ||
| var containerLogsToRelease *logs.LogDescriptorSet | ||
|
|
||
| if c.containerLogSource == nil { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this removed and why is it OK to remove? |
||
| // We haven't completed initialization for any resources yet | ||
| return | ||
| } | ||
| c.lock.Lock() | ||
|
|
||
| switch evt.Type { | ||
|
|
||
| case watch.Added, watch.Modified: | ||
| // "watch.Added" does not necessarily mean that the resource was just created. | ||
| // It really means that the resource was added to the watch stream (has been observed for the first time). | ||
| deletionRequested := ctr.DeletionTimestamp != nil && !ctr.DeletionTimestamp.IsZero() | ||
| if deletionRequested { | ||
| if c.containerLogs != nil { | ||
| containerLogsToRelease = c.containerLogs | ||
| } | ||
| } | ||
|
|
||
| if ctr.Status.State != apiv1.ContainerStateStarting && ctr.Status.State != apiv1.ContainerStateBuilding { | ||
| // If done starting the container, ensure startup logs stop streaming once they reach EOF | ||
| stopLogStreamsForContainer(c.startupLogStreams, ctr, "startup", log) | ||
| streamsToStop = append(streamsToStop, takeLogStreamsForContainer(c.startupLogStreams, ctr, "startup", logs.DelayStopFollowing)) | ||
| } | ||
|
|
||
| if ctr.Done() { | ||
| // If the container is done, ensure standard and system logs stop streaming once they reach EOF | ||
| stopLogStreamsForContainer(c.stdioLogStreams, ctr, "stdio", log) | ||
| stopLogStreamsForContainer(c.systemLogStreams, ctr, "system", log) | ||
| streamsToStop = append(streamsToStop, | ||
| takeLogStreamsForContainer(c.stdioLogStreams, ctr, "stdio", logs.DelayStopFollowing), | ||
| takeLogStreamsForContainer(c.systemLogStreams, ctr, "system", logs.DelayStopFollowing), | ||
| ) | ||
| } | ||
|
|
||
| case watch.Deleted: | ||
| // The resource was deleted, ensure any following log streams stop and cleanup their resources | ||
| stopLogStreamsForContainer(c.startupLogStreams, ctr, "startup", log) | ||
| stopLogStreamsForContainer(c.stdioLogStreams, ctr, "stdio", log) | ||
| stopLogStreamsForContainer(c.systemLogStreams, ctr, "system", log) | ||
| streamsToStop = append(streamsToStop, | ||
| takeLogStreamsForContainer(c.startupLogStreams, ctr, "startup", logs.DelayStopFollowing), | ||
| takeLogStreamsForContainer(c.stdioLogStreams, ctr, "stdio", logs.DelayStopFollowing), | ||
| takeLogStreamsForContainer(c.systemLogStreams, ctr, "system", logs.DelayStopFollowing), | ||
| ) | ||
|
|
||
| if c.containerLogs != nil { | ||
| // Need to stop the log streamer and any log watchers for this container (if any) as it is being deleted. | ||
| // It is OK to call ReleaseForResource() if the resource is not in the set, it is a no-op in that case. | ||
| c.containerLogs.ReleaseForResource(ctr.UID) | ||
| containerLogsToRelease = c.containerLogs | ||
| } | ||
| } | ||
|
|
||
| c.lock.Unlock() | ||
|
|
||
| stopLogStreamsForContainer(ctr, log, streamsToStop) | ||
| if containerLogsToRelease != nil { | ||
| containerLogsToRelease.ReleaseForResource(ctr.UID) | ||
| } | ||
| } | ||
|
|
||
| func stopLogStreamsForContainer( | ||
| func takeLogStreamsForContainer( | ||
| streams logs.LogStreamMop, | ||
| ctr *apiv1.Container, | ||
| streamKind string, | ||
| log logr.Logger, | ||
| ) { | ||
| cancelStreams func([]*usvc_io.FollowWriter), | ||
| ) containerStreamsToStop { | ||
| if ctrStreams, found := streams[ctr.UID]; found { | ||
| delete(streams, ctr.UID) | ||
| return containerStreamsToStop{ | ||
| streamKind: streamKind, | ||
| streams: maps.Values(ctrStreams), | ||
| cancelStreams: cancelStreams, | ||
| } | ||
| } | ||
|
|
||
| return containerStreamsToStop{} | ||
| } | ||
|
|
||
| func stopLogStreamsForContainer(ctr *apiv1.Container, log logr.Logger, streamsToStop []containerStreamsToStop) { | ||
| for _, streamGroup := range streamsToStop { | ||
| if len(streamGroup.streams) == 0 { | ||
| continue | ||
| } | ||
|
|
||
| if log.V(1).Enabled() { | ||
| log.V(1).Info(fmt.Sprintf("Stopping %s follow logs for container", streamKind), | ||
| log.V(1).Info(fmt.Sprintf("Stopping %s follow logs for container", streamGroup.streamKind), | ||
| "Container", ctr.Status.ContainerID, | ||
| "StreamCount", len(ctrStreams), | ||
| "StreamCount", len(streamGroup.streams), | ||
| ) | ||
| } | ||
|
|
||
| logs.DelayCancelFollowStreams(maps.Values(ctrStreams), (*usvc_io.FollowWriter).StopFollow) | ||
| streamGroup.cancelStreams(streamGroup.streams) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -352,31 +386,26 @@ func appropriatelyCancel(fw *usvc_io.FollowWriter, ctr *apiv1.Container, opts *a | |
| if !follow { | ||
| fw.StopFollow() // Will stop writing after first EOF is reached | ||
| } else if ctr.Done() { | ||
| logs.DelayCancelFollowStreams([]*usvc_io.FollowWriter{fw}, (*usvc_io.FollowWriter).StopFollow) | ||
| logs.DelayStopFollowing([]*usvc_io.FollowWriter{fw}) | ||
| } | ||
| } | ||
|
|
||
| func (c *containerLogStreamer) Dispose() error { | ||
| c.lock.Lock() | ||
|
|
||
| for _, w := range maps.FlattenValues(c.startupLogStreams) { | ||
| w.StopFollow() | ||
| } | ||
| streamsToCancel := maps.FlattenValues(c.startupLogStreams) | ||
| c.startupLogStreams = make(logs.LogStreamMop) | ||
| for _, w := range maps.FlattenValues(c.stdioLogStreams) { | ||
| w.StopFollow() | ||
| } | ||
| streamsToCancel = append(streamsToCancel, maps.FlattenValues(c.stdioLogStreams)...) | ||
| c.stdioLogStreams = make(logs.LogStreamMop) | ||
| for _, w := range maps.FlattenValues(c.systemLogStreams) { | ||
| w.StopFollow() | ||
| } | ||
| streamsToCancel = append(streamsToCancel, maps.FlattenValues(c.systemLogStreams)...) | ||
| c.systemLogStreams = make(logs.LogStreamMop) | ||
|
|
||
| lds := c.containerLogs | ||
| c.containerLogs = nil | ||
|
|
||
| c.lock.Unlock() | ||
|
|
||
| logs.CancelFollowStreams(streamsToCancel) | ||
| if lds != nil { | ||
| return lds.Dispose() | ||
| } else { | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this removed and why is it OK not to close the file even though it is referenced by local variable only?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I know after reading the whole change... but you tell me 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FollowWriter is closing the source now, but I'm about to make that an explicit FollowWriterOption so that it's more clear why this isn't necessary.