fix:  the counter was not decremented when an error occurred (#3013)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: yizhenqiang <yizhenqiang@apache.org>
Fixes: #3012
diff --git a/filter/active/filter.go b/filter/active/filter.go
index ce72025..ab070fb 100644
--- a/filter/active/filter.go
+++ b/filter/active/filter.go
@@ -74,6 +74,8 @@
 	if err != nil {
 		result.SetError(err)
 		logger.Errorf("parse dubbo_invoke_start_time to int64 failed")
+		// When err is not nil, use default elapsed value of 1
+		base.EndCount(invoker.GetURL(), inv.MethodName(), 1, false)
 		return result
 	}
 	elapsed := base.CurrentTimeMillis() - startTime
diff --git a/filter/active/filter_test.go b/filter/active/filter_test.go
index 7c4a692..a864d27 100644
--- a/filter/active/filter_test.go
+++ b/filter/active/filter_test.go
@@ -20,6 +20,7 @@
 import (
 	"context"
 	"errors"
+	"fmt"
 	"strconv"
 	"testing"
 )
@@ -32,6 +33,7 @@
 
 import (
 	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
 	"dubbo.apache.org/dubbo-go/v3/protocol/base"
 	"dubbo.apache.org/dubbo-go/v3/protocol/invocation"
 	"dubbo.apache.org/dubbo-go/v3/protocol/mock"
@@ -40,7 +42,7 @@
 
 func TestFilterInvoke(t *testing.T) {
 	invoc := invocation.NewRPCInvocation("test", []any{"OK"}, make(map[string]any))
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LocalHostValue, constant.DefaultPort))
 	filter := activeFilter{}
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
@@ -57,7 +59,7 @@
 	invoc := invocation.NewRPCInvocation("test", []any{"OK"}, map[string]any{
 		dubboInvokeStartTime: strconv.FormatInt(c-int64(elapsed), 10),
 	})
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LocalHostValue, constant.DefaultPort))
 	filter := activeFilter{}
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
@@ -81,3 +83,133 @@
 	assert.True(t, urlStatus.GetLastRequestFailedTimestamp() != int64(0))
 	assert.True(t, methodStatus.GetLastRequestFailedTimestamp() != int64(0))
 }
+
+func TestFilterOnResponseWithDefer(t *testing.T) {
+	base.CleanAllStatus()
+
+	// Test scenario 1: dubboInvokeStartTime is parsed successfully and the result is correct.
+	t.Run("ParseSuccessAndResultSuccess", func(t *testing.T) {
+		defer base.CleanAllStatus()
+
+		c := base.CurrentTimeMillis()
+		invoc := invocation.NewRPCInvocation("test1", []any{"OK"}, map[string]any{
+			dubboInvokeStartTime: strconv.FormatInt(c, 10),
+		})
+		url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LocalHostValue, constant.DefaultPort))
+		filter := activeFilter{}
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		invoker := mock.NewMockInvoker(ctrl)
+		invoker.EXPECT().GetURL().Return(url).Times(1)
+		rpcResult := &result.RPCResult{}
+
+		filter.OnResponse(context.TODO(), rpcResult, invoker, invoc)
+
+		methodStatus := base.GetMethodStatus(url, "test1")
+		urlStatus := base.GetURLStatus(url)
+
+		assert.Equal(t, int32(1), methodStatus.GetTotal())
+		assert.Equal(t, int32(1), urlStatus.GetTotal())
+		assert.Equal(t, int32(0), methodStatus.GetFailed())
+		assert.Equal(t, int32(0), urlStatus.GetFailed())
+		assert.Equal(t, int32(0), methodStatus.GetSuccessiveRequestFailureCount())
+		assert.Equal(t, int32(0), urlStatus.GetSuccessiveRequestFailureCount())
+		assert.True(t, methodStatus.GetTotalElapsed() >= int64(0))
+		assert.True(t, urlStatus.GetTotalElapsed() >= int64(0))
+	})
+
+	// Test scenario 2: dubboInvokeStartTime is parsed successfully, but the result is incorrect
+	t.Run("ParseSuccessAndResultFailed", func(t *testing.T) {
+		defer base.CleanAllStatus()
+
+		c := base.CurrentTimeMillis()
+		invoc := invocation.NewRPCInvocation("test2", []any{"OK"}, map[string]any{
+			dubboInvokeStartTime: strconv.FormatInt(c, 10),
+		})
+		url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LocalHostValue, constant.DefaultPort))
+		filter := activeFilter{}
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		invoker := mock.NewMockInvoker(ctrl)
+		invoker.EXPECT().GetURL().Return(url).Times(1)
+		rpcResult := &result.RPCResult{
+			Err: errors.New("test error"),
+		}
+
+		filter.OnResponse(context.TODO(), rpcResult, invoker, invoc)
+
+		methodStatus := base.GetMethodStatus(url, "test2")
+		urlStatus := base.GetURLStatus(url)
+
+		assert.Equal(t, int32(1), methodStatus.GetTotal())
+		assert.Equal(t, int32(1), urlStatus.GetTotal())
+		assert.Equal(t, int32(1), methodStatus.GetFailed())
+		assert.Equal(t, int32(1), urlStatus.GetFailed())
+		assert.Equal(t, int32(1), methodStatus.GetSuccessiveRequestFailureCount())
+		assert.Equal(t, int32(1), urlStatus.GetSuccessiveRequestFailureCount())
+		assert.True(t, methodStatus.GetFailedElapsed() >= int64(0))
+		assert.True(t, urlStatus.GetFailedElapsed() >= int64(0))
+		assert.True(t, urlStatus.GetLastRequestFailedTimestamp() != int64(0))
+		assert.True(t, methodStatus.GetLastRequestFailedTimestamp() != int64(0))
+	})
+
+	// Test Scenario 3: dubboInvokeStartTime parsing failed (non-numeric string)
+	t.Run("ParseFailedWithInvalidString", func(t *testing.T) {
+		defer base.CleanAllStatus()
+
+		invoc := invocation.NewRPCInvocation("test3", []any{"OK"}, map[string]any{
+			dubboInvokeStartTime: "invalid-time",
+		})
+		url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LocalHostValue, constant.DefaultPort))
+		filter := activeFilter{}
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		invoker := mock.NewMockInvoker(ctrl)
+		invoker.EXPECT().GetURL().Return(url).Times(1)
+		rpcResult := &result.RPCResult{}
+
+		result := filter.OnResponse(context.TODO(), rpcResult, invoker, invoc)
+		assert.NotNil(t, result.Error())
+
+		methodStatus := base.GetMethodStatus(url, "test3")
+		urlStatus := base.GetURLStatus(url)
+
+		// Verification count and status - should use the default duration of 1 and be marked as failed
+		assert.Equal(t, int32(1), methodStatus.GetTotal())
+		assert.Equal(t, int32(1), urlStatus.GetTotal())
+		assert.Equal(t, int32(1), methodStatus.GetFailed())
+		assert.Equal(t, int32(1), urlStatus.GetFailed())
+		assert.Equal(t, int32(1), methodStatus.GetSuccessiveRequestFailureCount())
+		assert.Equal(t, int32(1), urlStatus.GetSuccessiveRequestFailureCount())
+		assert.True(t, methodStatus.GetFailedElapsed() >= int64(1))
+		assert.True(t, urlStatus.GetFailedElapsed() >= int64(1))
+	})
+
+	// Test scenario 4: dubboInvokeStartTime does not exist (use the default value 0)
+	t.Run("ParseFailedWithDefaultValue", func(t *testing.T) {
+		defer base.CleanAllStatus()
+
+		invoc := invocation.NewRPCInvocation("test4", []any{"OK"}, make(map[string]any))
+		url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LocalHostValue, constant.DefaultPort))
+		filter := activeFilter{}
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		invoker := mock.NewMockInvoker(ctrl)
+		invoker.EXPECT().GetURL().Return(url).Times(1)
+		rpcResult := &result.RPCResult{}
+
+		filter.OnResponse(context.TODO(), rpcResult, invoker, invoc)
+
+		methodStatus := base.GetMethodStatus(url, "test4")
+		urlStatus := base.GetURLStatus(url)
+
+		assert.Equal(t, int32(1), methodStatus.GetTotal())
+		assert.Equal(t, int32(1), urlStatus.GetTotal())
+		assert.Equal(t, int32(0), methodStatus.GetFailed())
+		assert.Equal(t, int32(0), urlStatus.GetFailed())
+		assert.Equal(t, int32(0), methodStatus.GetSuccessiveRequestFailureCount())
+		assert.Equal(t, int32(0), urlStatus.GetSuccessiveRequestFailureCount())
+		assert.True(t, methodStatus.GetTotalElapsed() > int64(0))
+		assert.True(t, urlStatus.GetTotalElapsed() > int64(0))
+	})
+}