Skip to content

Commit f613742

Browse files
committed
feat(serve): Split timeout to idle timeout and max timeout
1 parent de55015 commit f613742

File tree

7 files changed

+95
-46
lines changed

7 files changed

+95
-46
lines changed

cmd/serve/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func run(cmd *cobra.Command, args []string) (err error) {
6060

6161
if telnet := server.NewTelnet(cmd.Flags()); telnet.Enabled {
6262
api.TelnetEnabled = true
63+
server.LoadDeprecated(cmd.Flags())
6364
group.Go(func() error {
6465
return telnet.Listen(ctx, &m)
6566
})

docs/ascii-movie_serve.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ ascii-movie serve [movie] [flags]
1313
--api-enabled Enables API listener (default true)
1414
--concurrent-streams uint Number of concurrent streams allowed from an IP address. Set to 0 to disable. (default 10)
1515
-h, --help help for serve
16+
--idle-timeout duration Idle connection timeout. (default 15m0s)
17+
--max-timeout duration Absolute connection timeout. (default 2h0m0s)
1618
--speed float Playback speed multiplier. Must be greater than 0. (default 1)
1719
--ssh-address string SSH listen address (default ":22")
1820
--ssh-enabled Enables SSH listener (default true)
1921
--ssh-host-key strings SSH host key file path
2022
--ssh-host-key-data strings SSH host key PEM data
2123
--telnet-address string Telnet listen address (default ":23")
2224
--telnet-enabled Enables Telnet listener (default true)
23-
--timeout duration Maximum amount of time that a connection may stay active. (default 1h0m0s)
2425
```
2526

2627
### Options inherited from parent commands

internal/movie/player.go

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ type Player struct {
6666

6767
keymap keymap
6868
helpViewCache string
69-
70-
timeoutCtx context.Context
71-
timeoutCancel context.CancelFunc
7269
}
7370

7471
func (p Player) Init() tea.Cmd {
@@ -148,12 +145,7 @@ func (p Player) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
148145
if p.log != nil {
149146
p.log.Info("Disconnected early")
150147
}
151-
if p.playCancel != nil {
152-
p.playCancel()
153-
}
154-
if p.timeoutCancel != nil {
155-
p.timeoutCancel()
156-
}
148+
p.clearTimeouts()
157149
return p, tea.Quit
158150
case PlayerOption:
159151
p.optionViewStale = true
@@ -227,8 +219,7 @@ func (p *Player) OptionsView() string {
227219

228220
func (p *Player) pause() tea.Cmd {
229221
p.clearTimeouts()
230-
p.timeoutCtx, p.timeoutCancel = context.WithCancel(context.Background())
231-
return tick(p.timeoutCtx, 15*time.Minute, Quit())
222+
return nil
232223
}
233224

234225
func (p *Player) play() tea.Cmd {
@@ -247,7 +238,4 @@ func (p *Player) clearTimeouts() {
247238
if p.playCancel != nil {
248239
p.playCancel()
249240
}
250-
if p.timeoutCancel != nil {
251-
p.timeoutCancel()
252-
}
253241
}

internal/server/flags.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ const (
1818

1919
ConcurrentStreamsFlag = "concurrent-streams"
2020
TimeoutFlag = "timeout"
21+
IdleTimeoutFlag = "idle-timeout"
22+
MaxTimeoutFlag = "max-timeout"
2123
)
2224

2325
var (
2426
concurrentStreams uint
25-
timeout time.Duration
27+
idleTimeout time.Duration
28+
maxTimeout time.Duration
2629
)
2730

2831
func Flags(flags *flag.FlagSet) {
@@ -38,5 +41,21 @@ func Flags(flags *flag.FlagSet) {
3841
flags.String(ApiFlagPrefix+AddressFlag, "127.0.0.1:1977", "API listen address")
3942

4043
flags.UintVar(&concurrentStreams, ConcurrentStreamsFlag, 10, "Number of concurrent streams allowed from an IP address. Set to 0 to disable.")
41-
flags.DurationVar(&timeout, TimeoutFlag, time.Hour, "Maximum amount of time that a connection may stay active.")
44+
flags.DurationVar(&idleTimeout, IdleTimeoutFlag, 15*time.Minute, "Idle connection timeout.")
45+
flags.DurationVar(&maxTimeout, MaxTimeoutFlag, 2*time.Hour, "Absolute connection timeout.")
46+
47+
flags.Duration(TimeoutFlag, time.Hour, "Maximum amount of time that a connection may stay active.")
48+
if err := flags.MarkDeprecated(TimeoutFlag, "please use --idle-timeout and --max-timeout instead."); err != nil {
49+
panic(err)
50+
}
51+
}
52+
53+
func LoadDeprecated(flags *flag.FlagSet) {
54+
if flags.Lookup(TimeoutFlag).Changed {
55+
d, err := flags.GetDuration(TimeoutFlag)
56+
if err == nil {
57+
idleTimeout = d
58+
maxTimeout = d
59+
}
60+
}
4261
}

internal/server/idleconn/conn.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package idleconn
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net"
7+
"os"
8+
"time"
9+
)
10+
11+
func New(conn net.Conn, idleTimeout, maxTimeout time.Duration, cancel context.CancelFunc) net.Conn {
12+
return &Conn{
13+
Conn: conn,
14+
idleTimeout: idleTimeout,
15+
maxDeadline: time.Now().Add(maxTimeout),
16+
cancel: cancel,
17+
}
18+
}
19+
20+
type Conn struct {
21+
net.Conn
22+
idleTimeout time.Duration
23+
maxDeadline time.Time
24+
cancel context.CancelFunc
25+
}
26+
27+
func (c *Conn) Write(p []byte) (int, error) {
28+
_ = c.updateDeadline()
29+
30+
n, err := c.Conn.Write(p)
31+
if errors.Is(err, os.ErrDeadlineExceeded) {
32+
c.cancel()
33+
}
34+
return n, err
35+
}
36+
37+
func (c *Conn) Read(b []byte) (int, error) {
38+
_ = c.updateDeadline()
39+
40+
n, err := c.Conn.Read(b)
41+
if errors.Is(err, os.ErrDeadlineExceeded) {
42+
c.cancel()
43+
}
44+
return n, err
45+
}
46+
47+
func (c *Conn) Close() error {
48+
if c.cancel != nil {
49+
c.cancel()
50+
}
51+
return c.Conn.Close()
52+
}
53+
54+
func (c *Conn) updateDeadline() error {
55+
if c.idleTimeout != 0 {
56+
idleDeadline := time.Now().Add(c.idleTimeout)
57+
if idleDeadline.Before(c.maxDeadline) || c.maxDeadline.IsZero() {
58+
return c.Conn.SetDeadline(idleDeadline)
59+
}
60+
}
61+
return c.Conn.SetDeadline(c.maxDeadline)
62+
}

internal/server/ssh.go

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ func (s *SSHServer) Listen(ctx context.Context, m *movie.Movie) error {
4747

4848
sshOptions := []ssh.Option{
4949
wish.WithAddress(s.Address),
50+
wish.WithIdleTimeout(idleTimeout),
51+
wish.WithMaxTimeout(maxTimeout),
5052
wish.WithMiddleware(
5153
bubbletea.MiddlewareWithProgramHandler(s.Handler(m), termenv.ANSI256),
5254
s.TrackStream,
@@ -117,27 +119,12 @@ func (s *SSHServer) Handler(m *movie.Movie) bubbletea.ProgramHandler {
117119
profile := util.Profile(pty.Term)
118120

119121
player := movie.NewPlayer(m, logger, profile)
120-
program := tea.NewProgram(
122+
return tea.NewProgram(
121123
player,
122124
tea.WithInput(session),
123125
tea.WithOutput(session),
124126
tea.WithFPS(30),
125127
)
126-
127-
if timeout != 0 {
128-
go func() {
129-
timer := time.NewTimer(timeout)
130-
defer timer.Stop()
131-
select {
132-
case <-timer.C:
133-
program.Send(movie.Quit())
134-
case <-session.Context().Done():
135-
136-
}
137-
}()
138-
}
139-
140-
return program
141128
}
142129
}
143130

internal/server/telnet.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
tea "github.com/charmbracelet/bubbletea"
1212
"github.com/gabe565/ascii-movie/internal/movie"
13+
"github.com/gabe565/ascii-movie/internal/server/idleconn"
1314
"github.com/gabe565/ascii-movie/internal/server/telnet"
1415
"github.com/gabe565/ascii-movie/internal/util"
1516
flag "github.com/spf13/pflag"
@@ -61,7 +62,9 @@ func (s *TelnetServer) Listen(ctx context.Context, m *movie.Movie) error {
6162
serveGroup.Add(1)
6263
go func() {
6364
defer serveGroup.Done()
64-
s.Handler(serveCtx, conn, m)
65+
ctx, cancel := context.WithCancel(serveCtx)
66+
conn = idleconn.New(conn, idleTimeout, maxTimeout, cancel)
67+
s.Handler(ctx, conn, m)
6568
}()
6669
}
6770
}()
@@ -125,18 +128,6 @@ func (s *TelnetServer) Handler(ctx context.Context, conn net.Conn, m *movie.Movi
125128
tea.WithFPS(30),
126129
)
127130

128-
if timeout != 0 {
129-
go func() {
130-
timer := time.NewTimer(timeout)
131-
defer timer.Stop()
132-
select {
133-
case <-timer.C:
134-
cancel()
135-
case <-ctx.Done():
136-
}
137-
}()
138-
}
139-
140131
go func() {
141132
<-ctx.Done()
142133
program.Send(movie.Quit())

0 commit comments

Comments
 (0)