Fix a tracing context leak in SpringMVC plugin (#34)

* Once the springMVC span is created in beforeMethod, it must be stopped in afterMethod. Or it may cause memory leak.

Authored-by: furao <furao@didiglobal.com>
diff --git a/CHANGES.md b/CHANGES.md
index f39b0b9..be31fc2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -21,6 +21,8 @@
 * Add benchmark result for `exception-ignore` plugin and polish plugin guide.
 * Provide Alibaba Druid database connection pool plugin.
 * Provide HikariCP database connection pool plugin.
+* fix the bug that springMVC plugin will not stop span when exception throws.
+
 
 #### Documentation
 
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java
index 9e8493a..46a1742 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java
@@ -198,56 +198,58 @@
         Object request = runtimeContext.get(REQUEST_KEY_IN_RUNTIME_CONTEXT);
 
         if (request != null) {
-            StackDepth stackDepth = (StackDepth) runtimeContext.get(CONTROLLER_METHOD_STACK_DEPTH);
-            if (stackDepth == null) {
-                throw new IllegalMethodStackDepthException();
-            } else {
-                stackDepth.decrement();
-            }
-
-            AbstractSpan span = ContextManager.activeSpan();
-
-            if (stackDepth.depth() == 0) {
-                Object response = runtimeContext.get(RESPONSE_KEY_IN_RUNTIME_CONTEXT);
-                if (response == null) {
-                    throw new ServletResponseNotFoundException();
+            try {
+                StackDepth stackDepth = (StackDepth) runtimeContext.get(CONTROLLER_METHOD_STACK_DEPTH);
+                if (stackDepth == null) {
+                    throw new IllegalMethodStackDepthException();
+                } else {
+                    stackDepth.decrement();
                 }
 
-                Integer statusCode = null;
+                AbstractSpan span = ContextManager.activeSpan();
 
-                if (IS_SERVLET_GET_STATUS_METHOD_EXIST && HttpServletResponse.class.isAssignableFrom(response.getClass())) {
-                    statusCode = ((HttpServletResponse) response).getStatus();
-                } else if (ServerHttpResponse.class.isAssignableFrom(response.getClass())) {
-                    if (IS_SERVLET_GET_STATUS_METHOD_EXIST) {
-                        statusCode = ((ServerHttpResponse) response).getRawStatusCode();
+                if (stackDepth.depth() == 0) {
+                    Object response = runtimeContext.get(RESPONSE_KEY_IN_RUNTIME_CONTEXT);
+                    if (response == null) {
+                        throw new ServletResponseNotFoundException();
                     }
-                    Object context = runtimeContext.get(REACTIVE_ASYNC_SPAN_IN_RUNTIME_CONTEXT);
-                    if (context != null) {
-                        ((AbstractSpan[]) context)[0] = span.prepareForAsync();
+
+                    Integer statusCode = null;
+
+                    if (IS_SERVLET_GET_STATUS_METHOD_EXIST && HttpServletResponse.class.isAssignableFrom(response.getClass())) {
+                        statusCode = ((HttpServletResponse) response).getStatus();
+                    } else if (ServerHttpResponse.class.isAssignableFrom(response.getClass())) {
+                        if (IS_SERVLET_GET_STATUS_METHOD_EXIST) {
+                            statusCode = ((ServerHttpResponse) response).getRawStatusCode();
+                        }
+                        Object context = runtimeContext.get(REACTIVE_ASYNC_SPAN_IN_RUNTIME_CONTEXT);
+                        if (context != null) {
+                            ((AbstractSpan[]) context)[0] = span.prepareForAsync();
+                        }
+                    }
+
+                    if (statusCode != null && statusCode >= 400) {
+                        span.errorOccurred();
+                        Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode);
+                    }
+
+                    runtimeContext.remove(REACTIVE_ASYNC_SPAN_IN_RUNTIME_CONTEXT);
+                    runtimeContext.remove(REQUEST_KEY_IN_RUNTIME_CONTEXT);
+                    runtimeContext.remove(RESPONSE_KEY_IN_RUNTIME_CONTEXT);
+                    runtimeContext.remove(CONTROLLER_METHOD_STACK_DEPTH);
+                }
+
+                // Active HTTP parameter collection automatically in the profiling context.
+                if (!SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS && span.isProfiling()) {
+                    if (HttpServletRequest.class.isAssignableFrom(request.getClass())) {
+                        RequestUtil.collectHttpParam((HttpServletRequest) request, span);
+                    } else if (ServerHttpRequest.class.isAssignableFrom(request.getClass())) {
+                        RequestUtil.collectHttpParam((ServerHttpRequest) request, span);
                     }
                 }
-
-                if (statusCode != null && statusCode >= 400) {
-                    span.errorOccurred();
-                    Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode);
-                }
-
-                runtimeContext.remove(REACTIVE_ASYNC_SPAN_IN_RUNTIME_CONTEXT);
-                runtimeContext.remove(REQUEST_KEY_IN_RUNTIME_CONTEXT);
-                runtimeContext.remove(RESPONSE_KEY_IN_RUNTIME_CONTEXT);
-                runtimeContext.remove(CONTROLLER_METHOD_STACK_DEPTH);
+            } finally {
+                ContextManager.stopSpan();
             }
-
-            // Active HTTP parameter collection automatically in the profiling context.
-            if (!SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS && span.isProfiling()) {
-                if (HttpServletRequest.class.isAssignableFrom(request.getClass())) {
-                    RequestUtil.collectHttpParam((HttpServletRequest) request, span);
-                } else if (ServerHttpRequest.class.isAssignableFrom(request.getClass())) {
-                    RequestUtil.collectHttpParam((ServerHttpRequest) request, span);
-                }
-            }
-
-            ContextManager.stopSpan();
         }
 
         return ret;