Skip to content

Conversation

@percivalalb
Copy link

The XY of the problem is the fds indices used by systemd socket activation are left empty once activation.Listener is called. Any new fds typically take the lowest available fds, which means the expected socket-activated indices may be populated by other fds at any other point in the executing of the program.

I'm creating a library that enables zero-downtime restarts by execve'ing the same program which requires that the fds be populated in the expected indices. This is not possible if another part of the program is using those fds.

  • This gives 3 methods of control, leaving currently logic intac:
    • ConsumeFiles the default and matches the previous logic - closes the systemd fds passed in.
    • ReserveFiles "reserves" the systemd fds by atomically replacing them with fds pointing at /dev/null
    • ConserveFiles does nothing and keeps the systemd fds open
  • Also exposes the unsetEnv of the system

@percivalalb percivalalb changed the title Draft: Allow more control over socket-activated file descriptors activation: allow more control over socket-activated file descriptors May 4, 2024
Copy link
Collaborator

@kolyshkin kolyshkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you still want to work on this. If yes, here are my initial comments.

case ReserveFiles:
devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0755)
if err != nil {
return fmt.Errorf("accessing /dev/null: %w", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no need to wrap an error as os.Open returns PathError which already has the file name.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This particular error is wrapped by the errorDevNullSetup type now - as are a few other errors produced in this ReserveFiles case. Sorry this Errorf was from an old change in an earlier commit, and I hadn't squash as I iterated on my changes.

I'm thinking of changing the error message from

"setting up " + strconv.Itoa(e.fd) + " fd to /dev/null: " + e.err.Error()

to
"reserving FD #" + strconv.Itoa(e.fd) + " : " + e.err.Error()`

// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener}
func Listeners() ([]net.Listener, error) {
files := Files(true)
func Listeners(opts ...option) ([]net.Listener, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks existing API, so you probably need to add ListenersWithOptions or something like that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While techincally the function signature was different, I was hoping I could get away with it as consumers of the library would not have to update their code. I've added WithOptions functions to seperate the old and new for now.

// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig.
func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {
listeners, err := Listeners()
func TLSListeners(tlsConfig *tls.Config, opts ...option) ([]net.Listener, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

"syscall"
)

type ErrorDevNullSetup struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do this need to be public?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you're right. Especially since the fields are private too. Also at the moment this error isn't event returned up the chain (though I'd like to change that).

}
}

// Options takes some [option]s and produces a stuct containing the flags and settings.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is weird to refer to a private type in doc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. I've djusted how the options struct is populated to running a apply function on the type instead.

@percivalalb
Copy link
Author

Hi, thank you for the review. I'll resolve the comments over the next week

@percivalalb
Copy link
Author

I've rebase onto main and added some more commits with changes based on your comments. I will squash the comments down once the changes have been reviewed.

Before deployment I will push some unit tests.

if pc, err := net.FileListener(f); err == nil {
listeners[i] = pc

o.method.Apply(f)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any thoughts on how to handle the error from this? Previously this was a f.Close() and the error was ignored (would linux ever return an error anyway?). Now a few more things could happen - e.g. setting up the /dev/null stuff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants