From 8376c9bcd3c7392670eaf1cbadf78554d1017d60 Mon Sep 17 00:00:00 2001 From: Watanabe Takashi Date: Mon, 10 Nov 2025 23:14:38 +0900 Subject: [PATCH 1/4] WaitGroup.Go() --- config_base.go | 22 +++++++++++----------- exec_if.go | 25 +++++++++---------------- exec_if_test.go | 22 ++++++++++++---------- exec_os.go | 25 +++++++++++-------------- exec_os_test.go | 6 ++---- exec_wasmer.go | 18 +++++------------- go.mod | 4 +--- 7 files changed, 51 insertions(+), 71 deletions(-) diff --git a/config_base.go b/config_base.go index 1641860..efcb074 100644 --- a/config_base.go +++ b/config_base.go @@ -3,16 +3,16 @@ package main import "time" type SrvConfigBase struct { - Verbose bool `short:"v" long:"verbose" description:"log verbose"` - Quiet bool `short:"q" long:"quiet" description:"log quiet"` - Addr string `short:"l" long:"listen" default:"localhost:" value-name:"[host]:port"` - Proto string `long:"protocol" default:"tcp" value-name:"tcp/unix"` - Prefix string `short:"p" long:"prefix" default:"/" value-name:"url-prefix"` - BaseDir string `short:"b" long:"base-dir" default:"." value-name:"dirname"` - Suffix string `short:"s" long:"suffix" value-name:".ext"` - JSONLog bool `long:"json-log"` - Runner string `long:"runner" default:"os" value-name:"name"` - Version bool `short:"V" long:"version"` - OtelProvider string `long:"opentelemetry" choice:"stdout" choice:"zipkin" choice:"otlp" choice:"otlp-http"` + Verbose bool `short:"v" long:"verbose" description:"log verbose"` + Quiet bool `short:"q" long:"quiet" description:"log quiet"` + Addr string `short:"l" long:"listen" default:"localhost:" value-name:"[host]:port"` + Proto string `long:"protocol" default:"tcp" value-name:"tcp/unix"` + Prefix string `short:"p" long:"prefix" default:"/" value-name:"url-prefix"` + BaseDir string `short:"b" long:"base-dir" default:"." value-name:"dirname"` + Suffix string `short:"s" long:"suffix" value-name:".ext"` + JSONLog bool `long:"json-log"` + Runner string `long:"runner" default:"os" value-name:"name"` + Version bool `short:"V" long:"version"` + OtelProvider string `long:"opentelemetry" choice:"stdout" choice:"zipkin" choice:"otlp" choice:"otlp-http"` Timeout time.Duration `short:"t" long:"timeout" default:"1m"` } diff --git a/exec_if.go b/exec_if.go index 0e568c0..1672e24 100644 --- a/exec_if.go +++ b/exec_if.go @@ -30,10 +30,7 @@ type Runner interface { } // OutputFilter converts CGI output to http.ResponseWriter -func OutputFilter(stdout io.Reader, w http.ResponseWriter, wg *sync.WaitGroup) error { - if wg != nil { - defer wg.Done() - } +func OutputFilter(stdout io.Reader, w http.ResponseWriter) error { rd := bufio.NewReader(stdout) statusCode := http.StatusOK for { @@ -156,13 +153,12 @@ func RunBy(opts SrvConfig, runner Runner, w http.ResponseWriter, r *http.Request } pr, pw := io.Pipe() var wg sync.WaitGroup - wg.Add(1) - go func() { - if err := OutputFilter(pr, w, &wg); err != nil { + wg.Go(func() { + if err := OutputFilter(pr, w); err != nil { slog.Error("output filter", "error", err) } span.AddEvent("ofilter finished") - }() + }) _, span2 := otel.Tracer("").Start(ctx, "run") span2.SetAttributes(attribute.String("script", bn2)) if span2.IsRecording() { @@ -187,11 +183,8 @@ func RunBy(opts SrvConfig, runner Runner, w http.ResponseWriter, r *http.Request return nil } -// DoPipe calls io.Copy() and wg.Done() -func DoPipe(input io.Reader, output io.Writer, wg *sync.WaitGroup) error { - if wg != nil { - defer wg.Done() - } +// DoPipe calls io.Copy() +func DoPipe(input io.Reader, output io.Writer) error { ilen, err := io.Copy(output, input) if err != nil { slog.Error("pipe error:", "error", err) @@ -203,14 +196,14 @@ func DoPipe(input io.Reader, output io.Writer, wg *sync.WaitGroup) error { func timeoutWait(wg *sync.WaitGroup, timeout time.Duration) bool { c := make(chan struct{}) - go func(){ + go func() { defer close(c) wg.Wait() }() select { case <-c: - return false // normal + return false // normal case <-time.After(timeout): - return true // timeout + return true // timeout } } diff --git a/exec_if_test.go b/exec_if_test.go index f16acfe..5b45506 100644 --- a/exec_if_test.go +++ b/exec_if_test.go @@ -47,12 +47,13 @@ func TestDoPipeWriteClose(t *testing.T) { rd, wr := io.Pipe() var wg sync.WaitGroup wr.Close() - wg.Add(1) - err := DoPipe(rd, wr, &wg) + wg.Go(func() { + err := DoPipe(rd, wr) + if err != nil { + t.Errorf("pipe error: %s", err) + } + }) wg.Wait() - if err != nil { - t.Errorf("pipe error: %s", err) - } } func TestDoPipeReadClose(t *testing.T) { @@ -60,12 +61,13 @@ func TestDoPipeReadClose(t *testing.T) { rd, wr := io.Pipe() var wg sync.WaitGroup rd.Close() - wg.Add(1) - err := DoPipe(rd, wr, &wg) + wg.Go(func() { + err := DoPipe(rd, wr) + if err == nil { + t.Error("pipe no-error") + } + }) wg.Wait() - if err == nil { - t.Error("pipe no-error") - } } type runner1 struct{} diff --git a/exec_os.go b/exec_os.go index b80af52..5ff11c5 100644 --- a/exec_os.go +++ b/exec_os.go @@ -60,27 +60,24 @@ func (runner *OsRunner) Run(conf SrvConfig, cmdname string, envvar map[string]st } }() var wg sync.WaitGroup - wg.Add(1) - go func() { - if err := DoPipe(stdin, cmdStdin, &wg); err != nil { + wg.Go(func() { + if err := DoPipe(stdin, cmdStdin); err != nil { slog.Error("stdin", "error", err) } - }() - wg.Add(1) - go func() { - if err := DoPipe(cmdStderr, stderr, &wg); err != nil { + }) + wg.Go(func() { + if err := DoPipe(cmdStderr, stderr); err != nil { slog.Error("stderr", "error", err) } - }() - wg.Add(1) - go func() { - if err := DoPipe(cmdStdout, stdout, &wg); err != nil { + }) + wg.Go(func() { + if err := DoPipe(cmdStdout, stdout); err != nil { slog.Error("stdout", "error", err) } - }() - if timeoutWait(&wg, conf.Timeout){ + }) + if timeoutWait(&wg, conf.Timeout) { slog.Warn("timeout") - if err := cmd.Process.Kill(); err != nil{ + if err := cmd.Process.Kill(); err != nil { slog.Error("kill failed", "error", err) } return fmt.Errorf("timeout %v", conf.Timeout) diff --git a/exec_os_test.go b/exec_os_test.go index 5537d03..e4da754 100644 --- a/exec_os_test.go +++ b/exec_os_test.go @@ -56,11 +56,10 @@ func TestOsRun(t *testing.T) { runner := OsRunner{} conf := SrvConfig{} conf.Timeout = time.Duration(1000_000_000) - tmpd, err := os.MkdirTemp("", "") + tmpd, err := os.MkdirTemp("/var/tmp", "") if err != nil { t.Error("tmpdir", err) } - defer os.RemoveAll(tmpd) conf.BaseDir = tmpd ctx := context.Background() if err = os.WriteFile(filepath.Join(tmpd, "cmd1"), []byte("#! /bin/sh\n"), 0755); err != nil { @@ -70,8 +69,7 @@ func TestOsRun(t *testing.T) { stdin := io.NopCloser(&bytes.Buffer{}) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} - err = runner.Run(conf, "cmd1", env, stdin, stdout, stderr, ctx) - if err != nil { + if err = runner.Run(conf, "cmd1", env, stdin, stdout, stderr, ctx); err != nil { t.Error("error", err) } if stdout.Len() != 0 { diff --git a/exec_wasmer.go b/exec_wasmer.go index c16f5ba..4aca59e 100644 --- a/exec_wasmer.go +++ b/exec_wasmer.go @@ -18,10 +18,7 @@ import ( type WasmerRunner struct { } -func (runner *WasmerRunner) pipeStdout(wasiEnv *wasmer.WasiEnvironment, output io.Writer, wg *sync.WaitGroup) error { - if wg != nil { - defer wg.Done() - } +func (runner *WasmerRunner) pipeStdout(wasiEnv *wasmer.WasiEnvironment, output io.Writer) error { data := wasiEnv.ReadStdout() slog.Info("stdout", "length", len(data)) dlen, err := output.Write(data) @@ -33,10 +30,7 @@ func (runner *WasmerRunner) pipeStdout(wasiEnv *wasmer.WasiEnvironment, output i return nil } -func (runner *WasmerRunner) pipeStderr(wasiEnv *wasmer.WasiEnvironment, output io.Writer, wg *sync.WaitGroup) error { - if wg != nil { - defer wg.Done() - } +func (runner *WasmerRunner) pipeStderr(wasiEnv *wasmer.WasiEnvironment, output io.Writer) error { data := wasiEnv.ReadStderr() slog.Info("stderr", "length", len(data)) dlen, err := output.Write(data) @@ -96,11 +90,9 @@ func (runner *WasmerRunner) Run(conf SrvConfig, cmdname string, envvar map[strin return err } var wg sync.WaitGroup - wg.Add(1) - go runner.pipeStdout(wasiEnv, stdout, &wg) - wg.Add(1) - go runner.pipeStderr(wasiEnv, stderr, &wg) - if timeoutWait(&wg, conf.Timeout){ + wg.Go(func() { runner.pipeStdout(wasiEnv, stdout) }) + wg.Go(func() { runner.pipeStderr(wasiEnv, stderr) }) + if timeoutWait(&wg, conf.Timeout) { slog.Warn("timeout") return nil } diff --git a/go.mod b/go.mod index e22a587..96aa796 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/wtnb75/httpcgi -go 1.24.0 - -toolchain go1.24.1 +go 1.25.0 require ( github.com/bytecodealliance/wasmtime-go v1.0.0 From 258db37ae0cccbf491ab821f005748cb43b97817 Mon Sep 17 00:00:00 2001 From: Watanabe Takashi Date: Mon, 10 Nov 2025 23:28:24 +0900 Subject: [PATCH 2/4] golangci --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e05389..9cebb8a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: go-version: 'stable' cache: true - name: lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v9 - run: go mod tidy - run: go get . - name: Build From 35d49f622c01af03aff5cc59088fa3febe989919 Mon Sep 17 00:00:00 2001 From: Watanabe Takashi Date: Mon, 10 Nov 2025 23:38:07 +0900 Subject: [PATCH 3/4] build tag --- config.go | 1 - config_docker.go | 1 - exec_docker.go | 1 - exec_docker_test.go | 1 - exec_if.go | 2 +- exec_wasmer.go | 1 - exec_wasmer_test.go | 1 - exec_wasmtime.go | 1 - exec_wasmtime_test.go | 1 - exec_wazero.go | 1 - exec_wazero_test.go | 1 - main.go | 10 ++++++---- wasm_test.go | 1 - 13 files changed, 7 insertions(+), 16 deletions(-) diff --git a/config.go b/config.go index 7042b47..e1f5453 100644 --- a/config.go +++ b/config.go @@ -1,5 +1,4 @@ //go:build !docker -// +build !docker package main diff --git a/config_docker.go b/config_docker.go index 38d63e0..e74bb12 100644 --- a/config_docker.go +++ b/config_docker.go @@ -1,5 +1,4 @@ //go:build docker -// +build docker package main diff --git a/exec_docker.go b/exec_docker.go index 701e82d..3f49a6b 100644 --- a/exec_docker.go +++ b/exec_docker.go @@ -1,5 +1,4 @@ //go:build docker -// +build docker package main diff --git a/exec_docker_test.go b/exec_docker_test.go index 83e0b34..1b8a5aa 100644 --- a/exec_docker_test.go +++ b/exec_docker_test.go @@ -1,5 +1,4 @@ //go:build docker -// +build docker package main diff --git a/exec_if.go b/exec_if.go index 1672e24..425ab80 100644 --- a/exec_if.go +++ b/exec_if.go @@ -165,7 +165,7 @@ func RunBy(opts SrvConfig, runner Runner, w http.ResponseWriter, r *http.Request carrier := propagation.MapCarrier{} otel.GetTextMapPropagator().Inject(ctx, carrier) for k, v := range carrier { - escaped := strings.Replace(strings.ToUpper(k), "-", "_", -1) + escaped := strings.ReplaceAll(strings.ToUpper(k), "-", "_") env[fmt.Sprintf("HTTP_%s", escaped)] = v } } diff --git a/exec_wasmer.go b/exec_wasmer.go index 4aca59e..a0e001a 100644 --- a/exec_wasmer.go +++ b/exec_wasmer.go @@ -1,5 +1,4 @@ //go:build wasmer -// +build wasmer package main diff --git a/exec_wasmer_test.go b/exec_wasmer_test.go index de4f310..12bb7ea 100644 --- a/exec_wasmer_test.go +++ b/exec_wasmer_test.go @@ -1,5 +1,4 @@ //go:build wasmer -// +build wasmer package main diff --git a/exec_wasmtime.go b/exec_wasmtime.go index 56fe4ce..1b00b02 100644 --- a/exec_wasmtime.go +++ b/exec_wasmtime.go @@ -1,5 +1,4 @@ //go:build wasmtime -// +build wasmtime package main diff --git a/exec_wasmtime_test.go b/exec_wasmtime_test.go index 68dd6ed..9c74fcc 100644 --- a/exec_wasmtime_test.go +++ b/exec_wasmtime_test.go @@ -1,5 +1,4 @@ //go:build wasmtime -// +build wasmtime package main diff --git a/exec_wazero.go b/exec_wazero.go index 33aa983..8ef18a7 100644 --- a/exec_wazero.go +++ b/exec_wazero.go @@ -1,5 +1,4 @@ //go:build wazero -// +build wazero package main diff --git a/exec_wazero_test.go b/exec_wazero_test.go index c3b0622..4387710 100644 --- a/exec_wazero_test.go +++ b/exec_wazero_test.go @@ -1,5 +1,4 @@ //go:build wazero -// +build wazero package main diff --git a/main.go b/main.go index ec7cb18..5d98c7d 100644 --- a/main.go +++ b/main.go @@ -70,25 +70,27 @@ func main() { if err != nil { slog.Error("abs", "error", err) } - if opts.OtelProvider == "stdout" { + switch opts.OtelProvider { + + case "stdout": if fin, err := initOtelStdout(); err != nil { slog.Error("otel-stdout", "error", err) } else { defer fin() } - } else if opts.OtelProvider == "zipkin" { + case "zipkin": if fin, err := initOtelZipkin(); err != nil { slog.Error("otel-zipkin", "error", err) } else { defer fin() } - } else if opts.OtelProvider == "otlp" { + case "otlp": if fin, err := initOtelOtlp(); err != nil { slog.Error("otel-otlp", "error", err) } else { defer fin() } - } else if opts.OtelProvider == "otlp-http" { + case "otlp-http": if fin, err := initOtelOtlpHttp(); err != nil { slog.Error("otel-otlp-http", "error", err) } else { diff --git a/wasm_test.go b/wasm_test.go index 2e38d65..69f2931 100644 --- a/wasm_test.go +++ b/wasm_test.go @@ -1,5 +1,4 @@ //go:build wazero || wasmtime || wasmer -// +build wazero wasmtime wasmer package main From 49cea1ef1d4b3cc62ec8409e8e839ed9f6bb0da0 Mon Sep 17 00:00:00 2001 From: Watanabe Takashi Date: Mon, 10 Nov 2025 23:38:25 +0900 Subject: [PATCH 4/4] linter config --- .golangci.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..ff24cdd --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,28 @@ +version: "2" +linters: + enable: + - asciicheck + - bidichk + - canonicalheader + - copyloopvar + #- cyclop + #- funlen + #- gocognit + #- gocyclo + - maintidx + #- nestif + #- lll + - decorder + - dupword + - durationcheck + - errname + - gosmopolitan + - misspell + - predeclared + - sloglint + - staticcheck + disable: + - errcheck +formatters: + enable: + - gofmt