This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-go.git


The following commit(s) were added to refs/heads/main by this push:
     new b52ef57  fix(grpc): support Go 1.25 and 1.26 plugin scenarios (#252)
b52ef57 is described below

commit b52ef5762fab16d85704a7d35b8968165d351b85
Author: 何嘉伟 <[email protected]>
AuthorDate: Thu Jul 30 13:28:05 2026 +0800

    fix(grpc): support Go 1.25 and 1.26 plugin scenarios (#252)
---
 CHANGES.md                                         |   4 +
 .../advanced-features/manual-apis/toolkit-trace.md |  16 +++
 docs/en/agent/support-plugins.md                   |   6 +-
 go.mod                                             |   2 +-
 go.sum                                             |   4 +-
 go.work.sum                                        |   7 +-
 plugins/grpc/server_handleStream_interceptor.go    |  17 ++-
 .../grpc/server_handleStream_interceptor_test.go   | 132 +++++++++++++++++++++
 plugins/grpc/server_sendresponse_interceptor.go    |  19 +--
 plugins/toolkit-activation/instrument.go           |  19 ++-
 .../trace/span_ref_intercepter.go                  |  58 +++++++++
 test/plugins/runner-helper/go.sum                  |  12 ++
 test/plugins/runner-helper/main_test.go            |  99 ++++++++++++++++
 .../runner-helper/templates/docker-compose.tpl     |   2 +-
 .../plugins/runner-helper/templates/dockerfile.tpl |   6 +
 test/plugins/runner-helper/templates/scenarios.tpl |   8 +-
 .../runner-helper/templates/wsl-scenarios.tpl      |   8 +-
 test/plugins/scenarios/gin/plugin.yml              |   8 +-
 test/plugins/scenarios/grpc/plugin.yml             |   6 +
 .../scenarios/trace-activation/config/excepted.yml |  24 ++++
 test/plugins/scenarios/trace-activation/main.go    |   1 +
 test/plugins/scenarios/trace-activation/plugin.yml |   2 +-
 .../scenarios/trace-activation/test_service.go     |  10 ++
 toolkit/trace/span.go                              |  11 ++
 24 files changed, 455 insertions(+), 26 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 6cdbf33..51b834f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,13 +13,17 @@ Release Notes.
 * Replace external `goapi` dependency with in-repo generated protocols. 
 * Support pprof profiling. 
 * Align the agent with the supported Go releases (retire EOL Go 1.19-1.23): 
publish Go 1.24, 1.25, 1.26 base images, bump the module `go.mod` floor to Go 
1.24, and run the CI build, plugin, and e2e jobs on Go 1.24-1.26.
+* Support managing toolkit spans across goroutines through `SpanRef`.
 
 #### Plugins
 
+* Support gRPC v1.81.1 with Go 1.25 and Go 1.26.
+
 #### Documentation
 
 #### Bug Fixes
 
+* Fix gRPC server tracing with recent internal stream types.
 * Fix plugin interceptors bypassed on Windows.
 * Fix wrong tracing context switch when trace ignore plugin activated.
 * Fix data race when sending trace data to reporter.
diff --git a/docs/en/advanced-features/manual-apis/toolkit-trace.md 
b/docs/en/advanced-features/manual-apis/toolkit-trace.md
index 0b3da0c..5088448 100644
--- a/docs/en/advanced-features/manual-apis/toolkit-trace.md
+++ b/docs/en/advanced-features/manual-apis/toolkit-trace.md
@@ -73,6 +73,22 @@ Use `trace.StopSpan()` API to stop current span
 trace.StopSpan()
 ```
 
+When the application retains the returned `SpanRef`, use `SpanRef.End()` to
+finish that exact span and `SpanRef.SetOperationName()` to rename it:
+
+```go
+spanRef, err := trace.CreateLocalSpan("originalName")
+if err == nil {
+    spanRef.SetOperationName("operationName")
+    spanRef.End()
+}
+```
+
+`SpanRef.End()` must be called from the goroutine that created the span and in
+LIFO order. For cross-goroutine work, use the asynchronous lifecycle described
+below. `trace.StopSpan()` remains available for applications that manage spans
+through the current tracing context.
+
 ### Add Span’s Tag and Log
 
 Use `trace.AddLog()` to record log in span.
diff --git a/docs/en/agent/support-plugins.md b/docs/en/agent/support-plugins.md
index 095bb44..4a50226 100644
--- a/docs/en/agent/support-plugins.md
+++ b/docs/en/agent/support-plugins.md
@@ -3,7 +3,7 @@ The following plugins provide the distributed tracing 
capability, and the OAP ba
 metrics based on the tracing data.
 
 * HTTP Server
-  * `gin`: [Gin](https://github.com/gin-gonic/gin) tested v1.7.0 to v1.9.0.
+  * `gin`: [Gin](https://github.com/gin-gonic/gin) tested v1.7.0 to v1.9.0 and 
v1.10.1.
   * `http`: [Native HTTP](https://pkg.go.dev/net/http) tested go v1.24 to go 
v1.26.
   * `go-restfulv3`: [Go-Restful](https://github.com/emicklei/go-restful) 
tested v3.7.1 to 3.10.2.
   * `mux`: [Mux](https://github.com/gorilla/mux) tested v1.7.0 to v1.8.0.
@@ -19,7 +19,7 @@ metrics based on the tracing data.
   * `dubbo`: [Dubbo](https://github.com/apache/dubbo-go) tested v3.0.1 to 
v3.0.5.
   * `kratosv2`: [Kratos](https://github.com/go-kratos/kratos) tested v2.3.1 to 
v2.6.2.
   * `microv4`: [Go-Micro](https://github.com/go-micro/go-micro) tested v4.6.0 
to v4.10.2.
-  * `grpc` : [gRPC](https://github.com/grpc/grpc-go) tested v1.55.0 to v1.64.0.
+  * `grpc` : [gRPC](https://github.com/grpc/grpc-go) tested v1.55.0 to v1.81.1.
 * Database Client
   * `gorm`: [GORM](https://github.com/go-gorm/gorm) tested v1.22.0 to v1.25.10.
     * [MySQL Driver](https://github.com/go-gorm/mysql)
@@ -46,4 +46,4 @@ The meter plugin provides the advanced metrics collections.
 The logging plugin provides the advanced logging collections.
 
 * `logrus`: [Logrus](https://github.com/sirupsen/logrus) tested v1.8.2 to 
v1.9.3.
-* `zap`: [Zap](http://go.uber.org/zap) tested v1.17.0 to v1.24.0.
\ No newline at end of file
+* `zap`: [Zap](http://go.uber.org/zap) tested v1.17.0 to v1.24.0.
diff --git a/go.mod b/go.mod
index ba1c9fe..5aad77e 100644
--- a/go.mod
+++ b/go.mod
@@ -18,5 +18,5 @@ require (
        github.com/google/go-cmp v0.5.9 // indirect
        github.com/klauspost/compress v1.15.9 // indirect
        github.com/pierrec/lz4/v4 v4.1.15 // indirect
-       google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // 
indirect
+       google.golang.org/genproto/googleapis/rpc 
v0.0.0-20230526161137-0005af68ea54 // indirect
 )
diff --git a/go.sum b/go.sum
index f02da08..6019143 100644
--- a/go.sum
+++ b/go.sum
@@ -65,8 +65,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod 
h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.1.12/go.mod 
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 
h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod 
h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230526161137-0005af68ea54 
h1:wQvmPUaH4JVFCzNAL9ShNjezVoq3OhlinNMLYSAN9Vg=
+google.golang.org/genproto/googleapis/rpc 
v0.0.0-20230526161137-0005af68ea54/go.mod 
h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
 google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
 google.golang.org/grpc v1.55.0/go.mod 
h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod 
h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
diff --git a/go.work.sum b/go.work.sum
index c50e493..8341850 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -1840,10 +1840,13 @@ google.golang.org/genproto 
v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f7
 google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod 
h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
 google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod 
h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
 google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod 
h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
-google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 
h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M=
-google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod 
h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
+google.golang.org/genproto v0.0.0-20230525234025-438c736192d0 
h1:x1vNwUhVOcsYoKyEGCZBH694SBmmBjA2EfauFVEI2+M=
+google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod 
h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY=
+google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a 
h1:HiYVD+FGJkTo+9zj1gqz0anapsa1JxjiSrN+BJKyUmE=
+google.golang.org/genproto/googleapis/api 
v0.0.0-20230525234020-1aefcd67740a/go.mod 
h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8=
 google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 
h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ=
 google.golang.org/genproto/googleapis/api 
v0.0.0-20230525234035-dd9d682886f9/go.mod 
h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
+google.golang.org/genproto/googleapis/rpc 
v0.0.0-20230525234015-3fc162c6f38a/go.mod 
h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 
h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM=
 google.golang.org/genproto/googleapis/rpc 
v0.0.0-20230525234030-28d5490b6b19/go.mod 
h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod 
h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
diff --git a/plugins/grpc/server_handleStream_interceptor.go 
b/plugins/grpc/server_handleStream_interceptor.go
index d4aef66..c835ece 100644
--- a/plugins/grpc/server_handleStream_interceptor.go
+++ b/plugins/grpc/server_handleStream_interceptor.go
@@ -18,6 +18,9 @@
 package grpc
 
 import (
+       "context"
+       "fmt"
+
        "google.golang.org/grpc/metadata"
 
        "github.com/apache/skywalking-go/plugins/core/operator"
@@ -27,8 +30,20 @@ import (
 type ServerHandleStreamInterceptor struct {
 }
 
+type serverTransportStream interface {
+       Method() string
+       Context() context.Context
+}
+
 func (h *ServerHandleStreamInterceptor) BeforeInvoke(invocation 
operator.Invocation) error {
-       stream := invocation.Args()[1].(*nativeStream)
+       args := invocation.Args()
+       if len(args) < 2 {
+               return fmt.Errorf("grpc handleStream expects at least 2 
arguments, got %d", len(args))
+       }
+       stream, ok := args[1].(serverTransportStream)
+       if !ok {
+               return fmt.Errorf("unsupported grpc server transport stream 
type %T", args[1])
+       }
        method := stream.Method()
        ctx := stream.Context()
        md, _ := metadata.FromIncomingContext(ctx)
diff --git a/plugins/grpc/server_handleStream_interceptor_test.go 
b/plugins/grpc/server_handleStream_interceptor_test.go
new file mode 100644
index 0000000..c950fb4
--- /dev/null
+++ b/plugins/grpc/server_handleStream_interceptor_test.go
@@ -0,0 +1,132 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with this work for
+// additional information regarding copyright ownership. Apache Software
+// Foundation (ASF) licenses this file to you under the Apache License, Version
+// 2.0 (the "License"); you may not use this file except in compliance with the
+// License. You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package grpc
+
+import (
+       "context"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/apache/skywalking-go/plugins/core"
+       "github.com/apache/skywalking-go/plugins/core/operator"
+       "github.com/apache/skywalking-go/plugins/core/tracing"
+)
+
+type testServerTransportStream struct {
+       ctx    context.Context
+       method string
+}
+
+func (s *testServerTransportStream) Context() context.Context {
+       return s.ctx
+}
+
+func (s *testServerTransportStream) Method() string {
+       return s.method
+}
+
+func TestServerHandleStreamAcceptsStreamInterface(t *testing.T) {
+       core.ResetTracingContext()
+       defer core.ResetTracingContext()
+
+       // gRPC v1.81 passes *transport.ServerStream where older releases passed
+       // *transport.Stream. Both expose this public method contract.
+       stream := &testServerTransportStream{
+               ctx:    context.Background(),
+               method: "/api.Echo/ServerStreamingEcho",
+       }
+       invocation := operator.NewInvocation(nil, nil, stream)
+       interceptor := &ServerHandleStreamInterceptor{}
+
+       if err := interceptor.BeforeInvoke(invocation); err != nil {
+               t.Fatal(err)
+       }
+       if invocation.GetContext() == nil {
+               t.Fatal("entry span was not stored in invocation context")
+       }
+       if err := interceptor.AfterInvoke(invocation); err != nil {
+               t.Fatal(err)
+       }
+
+       deadline := time.Now().Add(2 * time.Second)
+       for len(core.GetReportedSpans()) == 0 && time.Now().Before(deadline) {
+               time.Sleep(20 * time.Millisecond)
+       }
+       spans := core.GetReportedSpans()
+       if len(spans) != 1 {
+               t.Fatalf("reported spans = %d, want 1", len(spans))
+       }
+       if got := spans[0].OperationName(); got != 
"api.Echo.ServerStreamingEcho" {
+               t.Fatalf("operation name = %q", got)
+       }
+}
+
+func TestServerHandleStreamRejectsUnsupportedArgument(t *testing.T) {
+       invocation := operator.NewInvocation(nil, nil, struct{}{})
+       err := (&ServerHandleStreamInterceptor{}).BeforeInvoke(invocation)
+       if err == nil || !strings.Contains(err.Error(), "unsupported grpc 
server transport stream type") {
+               t.Fatalf("unexpected error: %v", err)
+       }
+}
+
+func TestServerSendResponseAcceptsStreamInterface(t *testing.T) {
+       core.ResetTracingContext()
+       defer core.ResetTracingContext()
+
+       root, err := tracing.CreateEntrySpan("api.Echo.UnaryEcho", func(string) 
(string, error) {
+               return "", nil
+       })
+       if err != nil {
+               t.Fatal(err)
+       }
+       stream := &testServerTransportStream{
+               ctx:    context.Background(),
+               method: "/api.Echo/UnaryEcho",
+       }
+       invocation := operator.NewInvocation(nil, nil, stream)
+       interceptor := &ServerSendResponseInterceptor{}
+
+       if err := interceptor.BeforeInvoke(invocation); err != nil {
+               t.Fatal(err)
+       }
+       if invocation.GetContext() == nil {
+               t.Fatal("send response span was not stored in invocation 
context")
+       }
+       if err := interceptor.AfterInvoke(invocation, error(nil)); err != nil {
+               t.Fatal(err)
+       }
+       root.End()
+
+       deadline := time.Now().Add(2 * time.Second)
+       for len(core.GetReportedSpans()) < 2 && time.Now().Before(deadline) {
+               time.Sleep(20 * time.Millisecond)
+       }
+       spans := core.GetReportedSpans()
+       if len(spans) != 2 {
+               t.Fatalf("reported spans = %d, want 2", len(spans))
+       }
+       found := false
+       for _, span := range spans {
+               if span.OperationName() == 
"api.Echo.UnaryEcho/Server/Response/SendResponse" {
+                       found = true
+                       break
+               }
+       }
+       if !found {
+               t.Fatal("send response span was not reported")
+       }
+}
diff --git a/plugins/grpc/server_sendresponse_interceptor.go 
b/plugins/grpc/server_sendresponse_interceptor.go
index 54cffa4..7cb39a3 100644
--- a/plugins/grpc/server_sendresponse_interceptor.go
+++ b/plugins/grpc/server_sendresponse_interceptor.go
@@ -29,16 +29,21 @@ func (h *ServerSendResponseInterceptor) 
BeforeInvoke(invocation operator.Invocat
        if tracing.ActiveSpan() == nil {
                return nil
        }
-       var cs *nativeStream
-       if arg1, ok := invocation.Args()[1].(*nativeStream); ok {
-               cs = arg1
-       } else if arg2, ok := invocation.Args()[2].(*nativeStream); ok {
-               cs = arg2
-       } else {
+       args := invocation.Args()
+       var stream serverTransportStream
+       for _, index := range []int{1, 2} {
+               if len(args) > index {
+                       if candidate, ok := 
args[index].(serverTransportStream); ok {
+                               stream = candidate
+                               break
+                       }
+               }
+       }
+       if stream == nil {
                return nil
        }
 
-       method := cs.Method()
+       method := stream.Method()
        s, err := tracing.CreateLocalSpan(formatOperationName(method, 
"/Server/Response/SendResponse"),
                tracing.WithLayer(tracing.SpanLayerRPCFramework),
                tracing.WithTag(tracing.TagURL, method),
diff --git a/plugins/toolkit-activation/instrument.go 
b/plugins/toolkit-activation/instrument.go
index 23a09da..62c1b24 100644
--- a/plugins/toolkit-activation/instrument.go
+++ b/plugins/toolkit-activation/instrument.go
@@ -115,7 +115,7 @@ func metricPoint() []*instrument.Point {
 }
 
 func tracePoint() []*instrument.Point {
-       return []*instrument.Point{
+       points := []*instrument.Point{
                {
                        PackagePath: "trace", At: 
instrument.NewStructEnhance("SpanRef"),
                },
@@ -167,6 +167,10 @@ func tracePoint() []*instrument.Point {
                        PackagePath: "trace", At: 
instrument.NewMethodEnhance("*SpanRef", "AddEvent"),
                        Interceptor: "AsyncAddEventInterceptor",
                },
+       }
+       points = append(points, spanRefLifecyclePoints()...)
+
+       return append(points, []*instrument.Point{
                {
                        PackagePath: "trace", At: 
instrument.NewStaticMethodEnhance("AddEvent"),
                        Interceptor: "AddEventInterceptor",
@@ -207,6 +211,19 @@ func tracePoint() []*instrument.Point {
                        PackagePath: "trace", At: 
instrument.NewStaticMethodEnhance("Error"),
                        Interceptor: "ErrorIntercepter",
                },
+       }...)
+}
+
+func spanRefLifecyclePoints() []*instrument.Point {
+       return []*instrument.Point{
+               {
+                       PackagePath: "trace", At: 
instrument.NewMethodEnhance("*SpanRef", "End"),
+                       Interceptor: "SpanRefEndInterceptor",
+               },
+               {
+                       PackagePath: "trace", At: 
instrument.NewMethodEnhance("*SpanRef", "SetOperationName"),
+                       Interceptor: "SpanRefSetOperationNameInterceptor",
+               },
        }
 }
 
diff --git a/plugins/toolkit-activation/trace/span_ref_intercepter.go 
b/plugins/toolkit-activation/trace/span_ref_intercepter.go
new file mode 100644
index 0000000..9f6023a
--- /dev/null
+++ b/plugins/toolkit-activation/trace/span_ref_intercepter.go
@@ -0,0 +1,58 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. The ASF licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package traceactivation
+
+import (
+       "github.com/apache/skywalking-go/plugins/core/operator"
+       "github.com/apache/skywalking-go/plugins/core/tracing"
+)
+
+type SpanRefEndInterceptor struct {
+}
+
+func (h *SpanRefEndInterceptor) BeforeInvoke(invocation operator.Invocation) 
error {
+       return nil
+}
+
+func (h *SpanRefEndInterceptor) AfterInvoke(invocation operator.Invocation, 
result ...interface{}) error {
+       if span := spanFromRef(invocation); span != nil {
+               span.End()
+       }
+       return nil
+}
+
+type SpanRefSetOperationNameInterceptor struct {
+}
+
+func (h *SpanRefSetOperationNameInterceptor) BeforeInvoke(invocation 
operator.Invocation) error {
+       return nil
+}
+
+func (h *SpanRefSetOperationNameInterceptor) AfterInvoke(invocation 
operator.Invocation, result ...interface{}) error {
+       if span := spanFromRef(invocation); span != nil {
+               span.SetOperationName(invocation.Args()[0].(string))
+       }
+       return nil
+}
+
+func spanFromRef(invocation operator.Invocation) tracing.Span {
+       enhanced, ok := invocation.CallerInstance().(operator.EnhancedInstance)
+       if !ok {
+               return nil
+       }
+       span, _ := enhanced.GetSkyWalkingDynamicField().(tracing.Span)
+       return span
+}
diff --git a/test/plugins/runner-helper/go.sum 
b/test/plugins/runner-helper/go.sum
index 808621e..5859bab 100644
--- a/test/plugins/runner-helper/go.sum
+++ b/test/plugins/runner-helper/go.sum
@@ -1,7 +1,19 @@
+github.com/creack/pty v1.1.9/go.mod 
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/kr/pretty v0.1.0/go.mod 
h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod 
h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod 
h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod 
h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod 
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/rogpeppe/go-internal v1.6.1/go.mod 
h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.9.0 
h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod 
h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod 
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/test/plugins/runner-helper/main_test.go 
b/test/plugins/runner-helper/main_test.go
new file mode 100644
index 0000000..749aed0
--- /dev/null
+++ b/test/plugins/runner-helper/main_test.go
@@ -0,0 +1,99 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more 
contributor
+// license agreements. See the NOTICE file distributed with this work for
+// additional information regarding copyright ownership. The ASF licenses this
+// file to you under the Apache License, Version 2.0 (the "License"); you may 
not
+// use this file except in compliance with the License. You may obtain a copy 
of
+// the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package main
+
+import (
+       "os"
+       "path/filepath"
+       "strings"
+       "testing"
+)
+
+func TestRenderDockerFileRefreshesWorkspaceGoDirective(t *testing.T) {
+       tests := []struct {
+               goVersion string
+               want      bool
+       }{
+               {goVersion: "1.17", want: false},
+               {goVersion: "1.18", want: true},
+               {goVersion: "1.25", want: true},
+               {goVersion: "1.26", want: true},
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.goVersion, func(t *testing.T) {
+                       projectDir := t.TempDir()
+                       workspaceDir := filepath.Join(projectDir, "workspace")
+                       if err := os.MkdirAll(workspaceDir, 0o755); err != nil {
+                               t.Fatal(err)
+                       }
+                       context := &Context{
+                               WorkSpaceDir: workspaceDir,
+                               ProjectDir:   projectDir,
+                               GoVersion:    tt.goVersion,
+                               ScenarioName: "grpc",
+                               CaseName:     "compatibility",
+                               GoAgentPath:  filepath.Join(projectDir, 
"agent"),
+                               Config:       &Config{StartScript: 
"./bin/startup.sh"},
+                       }
+
+                       if err := RenderDockerFile(context); err != nil {
+                               t.Fatal(err)
+                       }
+                       content, err := os.ReadFile(filepath.Join(workspaceDir, 
"Dockerfile"))
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       got := strings.Contains(string(content), "RUN go mod 
tidy\n# go mod tidy") &&
+                               strings.Contains(string(content), "RUN go work 
use .")
+                       if got != tt.want {
+                               t.Fatalf("workspace refresh present = %v, want 
%v", got, tt.want)
+                       }
+               })
+       }
+}
+
+func TestRenderScenariosAcceptsModernComposeMajorVersions(t *testing.T) {
+       workspaceDir := t.TempDir()
+       context := &Context{
+               WorkSpaceDir: workspaceDir,
+               ScenarioName: "http",
+       }
+
+       if err := RenderScenariosScript(context); err != nil {
+               t.Fatal(err)
+       }
+       if err := RenderWSLScenariosScript(context); err != nil {
+               t.Fatal(err)
+       }
+
+       for _, name := range []string{"scenarios.sh", "wsl-scenarios.sh"} {
+               content, err := os.ReadFile(filepath.Join(workspaceDir, name))
+               if err != nil {
+                       t.Fatal(err)
+               }
+               script := string(content)
+               for _, want := range []string{
+                       `compose_major=${compose_version#v}`,
+                       `compose_major=${compose_major%%.*}`,
+                       `(( compose_major >= 2 ))`,
+               } {
+                       if !strings.Contains(script, want) {
+                               t.Fatalf("generated %s does not contain %q", 
name, want)
+                       }
+               }
+       }
+}
diff --git a/test/plugins/runner-helper/templates/docker-compose.tpl 
b/test/plugins/runner-helper/templates/docker-compose.tpl
index a7410af..d46c72d 100644
--- a/test/plugins/runner-helper/templates/docker-compose.tpl
+++ b/test/plugins/runner-helper/templates/docker-compose.tpl
@@ -122,4 +122,4 @@ services:
       timeout: {{$service.HealthCheck.Timeout}}
       retries: {{$service.HealthCheck.Retries}}
     {{- end }}
-  {{- end }}
\ No newline at end of file
+  {{- end }}
diff --git a/test/plugins/runner-helper/templates/dockerfile.tpl 
b/test/plugins/runner-helper/templates/dockerfile.tpl
index f1b8fed..9ebf7e1 100644
--- a/test/plugins/runner-helper/templates/dockerfile.tpl
+++ b/test/plugins/runner-helper/templates/dockerfile.tpl
@@ -33,6 +33,12 @@ WORKDIR 
/skywalking-go/test/plugins/workspace/{{.Context.ScenarioName}}/{{.Conte
 RUN echo "replace github.com/apache/skywalking-go/toolkit => 
../../../../../toolkit" >> ./go.mod
 {{ end }}
 RUN go mod tidy
+{{ if .GreaterThanGo18 -}}
+# go mod tidy may raise the scenario module's Go directive to a patch release
+# (for example 1.25.0). Refresh the workspace directive so Go does not reject
+# the generated module as requiring a newer version than go.work.
+RUN go work use .
+{{ end }}
 
 ENV GO_BUILD_OPTS=" -toolexec \"/skywalking-go{{.ToolExecPath}}\" -a -work "
 
diff --git a/test/plugins/runner-helper/templates/scenarios.tpl 
b/test/plugins/runner-helper/templates/scenarios.tpl
index b91d5f3..437a79f 100644
--- a/test/plugins/runner-helper/templates/scenarios.tpl
+++ b/test/plugins/runner-helper/templates/scenarios.tpl
@@ -19,10 +19,12 @@
 set -ex
 
 compose_version=$(docker-compose version --short)
+compose_major=${compose_version#v}
+compose_major=${compose_major%%.*}
 
-if [[ $compose_version =~ ^(v)?1 ]]; then
+if [[ $compose_major == "1" ]]; then
     separator="_"
-elif [[ $compose_version =~ ^(v)?2 ]]; then
+elif [[ $compose_major =~ ^[0-9]+$ ]] && (( compose_major >= 2 )); then
     separator="-"
 else
     echo "Unsupported Docker Compose version: $compose_version"
@@ -54,4 +56,4 @@ else
     docker-compose -p ${project_name} -f {{.DockerComposeFilePath}} rm -f
 fi
 
-exit $status
\ No newline at end of file
+exit $status
diff --git a/test/plugins/runner-helper/templates/wsl-scenarios.tpl 
b/test/plugins/runner-helper/templates/wsl-scenarios.tpl
index 7d5137c..57abda4 100644
--- a/test/plugins/runner-helper/templates/wsl-scenarios.tpl
+++ b/test/plugins/runner-helper/templates/wsl-scenarios.tpl
@@ -66,10 +66,12 @@ sed -i "s/HTTP_HOST=127\.0\.0\.1/HTTP_HOST=$WINDOWS_HOST/g" 
validator.sh
 
 
 compose_version=$(docker-compose version --short)
+compose_major=${compose_version#v}
+compose_major=${compose_major%%.*}
 
-if [[ $compose_version =~ ^(v)?1 ]]; then
+if [[ $compose_major == "1" ]]; then
     separator="_"
-elif [[ $compose_version =~ ^(v)?2 ]]; then
+elif [[ $compose_major =~ ^[0-9]+$ ]] && (( compose_major >= 2 )); then
     separator="-"
 else
     echo "Unsupported Docker Compose version: $compose_version"
@@ -119,4 +121,4 @@ else
     docker-compose -p ${project_name} -f "docker-compose.yml" rm -f
 fi
 
-exit $status
\ No newline at end of file
+exit $status
diff --git a/test/plugins/scenarios/gin/plugin.yml 
b/test/plugins/scenarios/gin/plugin.yml
index 6de1dba..324fb9b 100644
--- a/test/plugins/scenarios/gin/plugin.yml
+++ b/test/plugins/scenarios/gin/plugin.yml
@@ -22,4 +22,10 @@ export-port: 8080
 support-version:
   - go: 1.24
     framework:
-      - v1.9.0
\ No newline at end of file
+      - v1.9.0
+  - go: 1.25
+    framework:
+      - v1.10.1
+  - go: 1.26
+    framework:
+      - v1.10.1
diff --git a/test/plugins/scenarios/grpc/plugin.yml 
b/test/plugins/scenarios/grpc/plugin.yml
index 9acaeb5..a273ce6 100644
--- a/test/plugins/scenarios/grpc/plugin.yml
+++ b/test/plugins/scenarios/grpc/plugin.yml
@@ -30,3 +30,9 @@ support-version:
       - v1.60.0
       - v1.62.0
       - v1.64.0
+  - go: 1.25
+    framework:
+      - v1.81.1
+  - go: 1.26
+    framework:
+      - v1.81.1
diff --git a/test/plugins/scenarios/trace-activation/config/excepted.yml 
b/test/plugins/scenarios/trace-activation/config/excepted.yml
index 27ad4d9..31e2e68 100644
--- a/test/plugins/scenarios/trace-activation/config/excepted.yml
+++ b/test/plugins/scenarios/trace-activation/config/excepted.yml
@@ -217,6 +217,30 @@ segmentItems:
             logs:
               - logEvent:
                   - { key: debug, value: foo }
+          - operationName: testSpanRefLifecycle_success
+            parentSpanId: 8
+            spanId: 15
+            spanLayer: Unknown
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 0
+            isError: false
+            spanType: Local
+            peer: ''
+            skipAnalysis: false
+            tags:
+              - { key: spanRefLifecycle, value: success }
+          - operationName: testSpanRefLifecycleSibling
+            parentSpanId: 8
+            spanId: 16
+            spanLayer: Unknown
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 0
+            isError: false
+            spanType: Local
+            peer: ''
+            skipAnalysis: false
           - operationName: GET:/consumer
             parentSpanId: -1
             spanId: 0
diff --git a/test/plugins/scenarios/trace-activation/main.go 
b/test/plugins/scenarios/trace-activation/main.go
index 25de3eb..c55df01 100644
--- a/test/plugins/scenarios/trace-activation/main.go
+++ b/test/plugins/scenarios/trace-activation/main.go
@@ -43,6 +43,7 @@ func consumerHandler(w http.ResponseWriter, r *http.Request) {
        testComponent()
        testAsyncInCrossGoroutine()
        testEvent()
+       testSpanRefLifecycle()
 }
 
 func main() {
diff --git a/test/plugins/scenarios/trace-activation/plugin.yml 
b/test/plugins/scenarios/trace-activation/plugin.yml
index 5e23a6e..d0aacc1 100644
--- a/test/plugins/scenarios/trace-activation/plugin.yml
+++ b/test/plugins/scenarios/trace-activation/plugin.yml
@@ -23,4 +23,4 @@ support-version:
   - go: 1.24
   - go: 1.25
   - go: 1.26
-toolkit: true
\ No newline at end of file
+toolkit: true
diff --git a/test/plugins/scenarios/trace-activation/test_service.go 
b/test/plugins/scenarios/trace-activation/test_service.go
index ac4fb6e..95f09b0 100644
--- a/test/plugins/scenarios/trace-activation/test_service.go
+++ b/test/plugins/scenarios/trace-activation/test_service.go
@@ -124,3 +124,13 @@ func testAsyncInCrossGoroutine() {
        }()
        <-ch
 }
+
+func testSpanRefLifecycle() {
+       span, _ := trace.CreateLocalSpan("testSpanRefLifecycle_failed")
+       span.SetOperationName("testSpanRefLifecycle_success")
+       span.SetTag("spanRefLifecycle", "success")
+       span.End()
+
+       sibling, _ := trace.CreateLocalSpan("testSpanRefLifecycleSibling")
+       sibling.End()
+}
diff --git a/toolkit/trace/span.go b/toolkit/trace/span.go
index 40df3bf..12630cf 100644
--- a/toolkit/trace/span.go
+++ b/toolkit/trace/span.go
@@ -40,6 +40,17 @@ func (*SpanRef) PrepareAsync() {
 func (*SpanRef) AsyncFinish() {
 }
 
+// End finishes the span referenced by SpanRef.
+//
+// End must be called from the goroutine that created the span and in LIFO
+// order. Use PrepareAsync, StopSpan, and AsyncFinish for cross-goroutine work.
+func (*SpanRef) End() {
+}
+
+// SetOperationName resets the operation name of the span referenced by 
SpanRef.
+func (*SpanRef) SetOperationName(string) {
+}
+
 // nolint
 func (*SpanRef) SetTag(key string, value string) {
 }

Reply via email to