Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true

[*.txt]
indent_style = space

[*.conf]
indent_style = space

[*.yml]
indent_size = 2
indent_style = space
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ go 1.23.3

require (
github.com/charmbracelet/glamour v0.10.0
github.com/google/go-cmp v0.7.0
github.com/miekg/dns v1.1.66
github.com/pion/stun/v3 v3.0.0
github.com/quic-go/quic-go v0.53.0
github.com/rbmk-project/common v0.21.0
github.com/rbmk-project/dnscore v0.13.0
github.com/rbmk-project/x v0.0.0-20250625213336-5718c136805c
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
golang.org/x/net v0.41.0
golang.org/x/sys v0.33.0
mvdan.cc/sh/v3 v3.11.0
)

Expand All @@ -37,7 +40,7 @@ require (
github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/quic-go v0.53.0 // indirect
github.com/rbmk-project/dnscore v0.13.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
Expand All @@ -48,9 +51,7 @@ require (
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.34.0 // indirect
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/curl/curl.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import (
"time"

"github.com/rbmk-project/common/cliutils"
"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/common/fsx"
"github.com/rbmk-project/rbmk/internal/markdown"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/spf13/pflag"
)

Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/curl/httplog.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"net/netip"
"time"

"github.com/rbmk-project/common/httpconntrace"
"github.com/rbmk-project/common/httpslog"
"github.com/rbmk-project/rbmk/pkg/common/httpconntrace"
"github.com/rbmk-project/rbmk/pkg/common/httpslog"
)

// httpDoAndLog performs the request and emits structured logs.
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/curl/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import (
"net/http"
"time"

"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/common/dialonce"
"github.com/rbmk-project/dnscore"
"github.com/rbmk-project/rbmk/internal/testable"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/rbmk-project/rbmk/pkg/dns/dnscore"
"github.com/rbmk-project/x/netcore"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/dig/dig.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
"strings"

"github.com/rbmk-project/common/cliutils"
"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/common/fsx"
"github.com/rbmk-project/rbmk/internal/markdown"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/spf13/pflag"
)

Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/dig/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import (
"time"

"github.com/miekg/dns"
"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/dnscore"
"github.com/rbmk-project/rbmk/internal/testable"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/rbmk-project/rbmk/pkg/dns/dnscore"
"github.com/rbmk-project/x/netcore"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/nc/nc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
"time"

"github.com/rbmk-project/common/cliutils"
"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/common/fsx"
"github.com/rbmk-project/rbmk/internal/markdown"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/spf13/pflag"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/nc/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
"net"
"time"

"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/rbmk/internal/testable"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/rbmk-project/x/netcore"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/stun/stun.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
"time"

"github.com/rbmk-project/common/cliutils"
"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/common/fsx"
"github.com/rbmk-project/rbmk/internal/markdown"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/spf13/pflag"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/stun/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
"time"

"github.com/pion/stun/v3"
"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/rbmk/internal/testable"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/rbmk-project/x/netcore"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/tar/tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import (
"path/filepath"

"github.com/rbmk-project/common/cliutils"
"github.com/rbmk-project/common/closepool"
"github.com/rbmk-project/rbmk/internal/markdown"
"github.com/rbmk-project/rbmk/pkg/common/closepool"
"github.com/spf13/pflag"
)

Expand Down
63 changes: 63 additions & 0 deletions pkg/common/closepool/closepool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-3.0-or-later

// Package closepool allows pooling [io.Closer] instances
// and closing them in a single operation.
package closepool

import (
"errors"
"io"
"slices"
"sync"
)

// CloserFunc allows to turn a suitable function into an [io.Closer].
type CloserFunc func() error

// Ensure that [CloserFunc] implements [io.Closer].
var _ io.Closer = CloserFunc(nil)

// Close implements io.Closer.
func (fx CloserFunc) Close() error {
return fx()
}

// Pool allows pooling a set of [io.Closer].
//
// The zero value is ready to use.
type Pool struct {
// handles contains the [io.Closer] to close.
handles []io.Closer

// mu provides mutual exclusion.
mu sync.Mutex
}

// Add adds a given [io.Closer] to the pool.
func (p *Pool) Add(conn io.Closer) {
p.mu.Lock()
p.handles = append(p.handles, conn)
p.mu.Unlock()
}

// Close closes all the [io.Closer] inside the pool iterating
// in backward order. Therefore, if one registers a TCP connection
// and then the corresponding TLS connection, the TLS connection
// is closed first. The returned error is the join of all the
// errors that occurred when closing connections.
func (p *Pool) Close() error {
// Lock and copy the [io.Closer] to close.
p.mu.Lock()
handles := p.handles
p.handles = nil
p.mu.Unlock()

// Close all the [io.Closer].
var errv []error
for _, handle := range slices.Backward(handles) {
if err := handle.Close(); err != nil {
errv = append(errv, err)
}
}
return errors.Join(errv...)
}
133 changes: 133 additions & 0 deletions pkg/common/closepool/closepool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-3.0-or-later

package closepool_test

import (
"errors"
"sync/atomic"
"testing"
"time"

"github.com/rbmk-project/rbmk/pkg/common/closepool"
)

// mockCloser implements io.Closer for testing
type mockCloser struct {
closed atomic.Int64
err error
}

// t0 is the time when we started running
var t0 = time.Now()

func (m *mockCloser) Close() error {
m.closed.Add(int64(time.Since(t0)))
return m.err
}

func TestCloserFunc(t *testing.T) {
var closed bool
pool := &closepool.Pool{}
pool.Add(closepool.CloserFunc(func() error {
closed = true
return nil
}))
pool.Close()
if !closed {
t.Error("expected closer to be closed")
}
}

func TestPool(t *testing.T) {
t.Run("successful close", func(t *testing.T) {
pool := closepool.Pool{}
m1 := &mockCloser{}
m2 := &mockCloser{}

pool.Add(m1)
pool.Add(m2)

err := pool.Close()
if err != nil {
t.Errorf("expected no error, got %v", err)
}

if m1.closed.Load() <= 0 {
t.Error("first closer was not closed")
}
if m2.closed.Load() <= 0 {
t.Error("second closer was not closed")
}
})

t.Run("close order", func(t *testing.T) {
pool := closepool.Pool{}

m1 := &mockCloser{
err: nil,
}
m2 := &mockCloser{
err: nil,
}

pool.Add(m1) // Added first
pool.Add(m2) // Added second

// Should close in reverse order
err := pool.Close()
if err != nil {
t.Errorf("expected no error, got %v", err)
}

if m1.closed.Load() <= m2.closed.Load() {
t.Error("expected m1 to be closed after m2")
}
})

t.Run("error handling", func(t *testing.T) {
pool := closepool.Pool{}
expectedErr1 := errors.New("close error #1")
expectedErr2 := errors.New("close error #2")

m1 := &mockCloser{err: expectedErr1}
m2 := &mockCloser{err: expectedErr2}

pool.Add(m1)
pool.Add(m2)

err := pool.Close()
if err == nil {
t.Fatalf("expected error, got nil")
}

t.Log(err)
if errors.Join(expectedErr2, expectedErr1).Error() != err.Error() {
t.Errorf("expected error to include both errors, got %v", err)
}
})

t.Run("concurrent usage", func(t *testing.T) {
pool := closepool.Pool{}
done := make(chan struct{})

// Concurrently add closers
go func() {
for i := 0; i < 100; i++ {
pool.Add(&mockCloser{})
}
close(done)
}()

// Add more closers from main goroutine
for i := 0; i < 100; i++ {
pool.Add(&mockCloser{})
}

<-done // Wait for goroutine to finish

err := pool.Close()
if err != nil {
t.Errorf("expected no error, got %v", err)
}
})
}
10 changes: 10 additions & 0 deletions pkg/common/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later

/*
Package common contains simple, common packages used by other packages.

See [dd-001-common.md] for more information.

[dd-001-common.md]: https://github.com/rbmk-project/rbmk-project.github.io/blob/main/docs/design/dd-001-common.md
*/
package common
Loading
Loading