### Elasticsearch Version

8.7.0-SNAPSHOT

### Installed Plugins

_No response_

### Java Version

_bundled_

### OS Version

N/A

### Problem Description

During benchmarking I noticed the following stack trace in the ES logs:

```
[2023-02-10T09:12:28,400][WARN ][r.suppressed             ] [elasticsearch-0] path: /_profiling/stacktraces, params: {}
java.lang.IllegalStateException: Channel is already closed
        at org.elasticsearch.rest.RestController$ResourceHandlingHttpChannel.close(RestController.java:772) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.rest.RestController$ResourceHandlingHttpChannel.sendResponse(RestController.java:759) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.rest.action.RestResponseListener.processResponse(RestResponseListener.java:26) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.rest.action.RestActionListener.onResponse(RestActionListener.java:38) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.rest.action.RestCancellableNodeClient$1.onResponse(RestCancellableNodeClient.java:87) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.rest.action.RestCancellableNodeClient$1.onResponse(RestCancellableNodeClient.java:81) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.client.internal.node.NodeClient$SafelyWrappedActionListener.onResponse(NodeClient.java:160) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.tasks.TaskManager$1.onResponse(TaskManager.java:209) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.tasks.TaskManager$1.onResponse(TaskManager.java:203) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.support.ContextPreservingActionListener.onResponse(ContextPreservingActionListener.java:31) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.lambda$applyInternal$2(SecurityActionFilter.java:165) ~[?:?]
        at org.elasticsearch.action.ActionListener$DelegatingFailureActionListener.onResponse(ActionListener.java:250) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.xpack.profiler.TransportGetProfilingAction$DetailsHandler.mayFinish(TransportGetProfilingAction.java:427) ~[?:?]
        at org.elasticsearch.xpack.profiler.TransportGetProfilingAction$DetailsHandler.onExecutableDetailsResponse(TransportGetProfilingAction.java:419) ~[?:?]
        at org.elasticsearch.xpack.profiler.TransportGetProfilingAction$5.onResponse(TransportGetProfilingAction.java:315) ~[?:?]
        at org.elasticsearch.xpack.profiler.TransportGetProfilingAction$5.onResponse(TransportGetProfilingAction.java:312) ~[?:?]
        at org.elasticsearch.client.internal.node.NodeClient$SafelyWrappedActionListener.onResponse(NodeClient.java:160) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.tasks.TaskManager$1.onResponse(TaskManager.java:209) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.tasks.TaskManager$1.onResponse(TaskManager.java:203) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.support.ContextPreservingActionListener.onResponse(ContextPreservingActionListener.java:31) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.lambda$applyInternal$2(SecurityActionFilter.java:165) ~[?:?]
        at org.elasticsearch.action.ActionListener$DelegatingFailureActionListener.onResponse(ActionListener.java:250) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.get.TransportMultiGetAction$1.finishHim(TransportMultiGetAction.java:155) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.get.TransportMultiGetAction$1.onResponse(TransportMultiGetAction.java:138) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.get.TransportMultiGetAction$1.onResponse(TransportMultiGetAction.java:130) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.client.internal.node.NodeClient$SafelyWrappedActionListener.onResponse(NodeClient.java:160) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.tasks.TaskManager$1.onResponse(TaskManager.java:209) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.tasks.TaskManager$1.onResponse(TaskManager.java:203) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.support.ContextPreservingActionListener.onResponse(ContextPreservingActionListener.java:31) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.lambda$applyInternal$2(SecurityActionFilter.java:165) ~[?:?]
        at org.elasticsearch.action.ActionListener$DelegatingFailureActionListener.onResponse(ActionListener.java:250) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.ActionListenerResponseHandler.handleResponse(ActionListenerResponseHandler.java:43) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.transport.TransportService$ContextRestoreResponseHandler.handleResponse(TransportService.java:1367) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.transport.TransportService$DirectResponseChannel.processResponse(TransportService.java:1466) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.transport.TransportService$DirectResponseChannel.sendResponse(TransportService.java:1437) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.transport.TaskTransportChannel.sendResponse(TaskTransportChannel.java:44) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.support.ChannelActionListener.lambda$onResponse$0(ChannelActionListener.java:38) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.ActionListener.run(ActionListener.java:567) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.support.ChannelActionListener.onResponse(ChannelActionListener.java:38) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.support.ChannelActionListener.onResponse(ChannelActionListener.java:20) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.ActionRunnable$2.accept(ActionRunnable.java:50) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.ActionRunnable$2.accept(ActionRunnable.java:47) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.action.ActionRunnable$3.doRun(ActionRunnable.java:72) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:958) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:26) ~[elasticsearch-8.8.0-SNAPSHOT.jar:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
        at java.lang.Thread.run(Thread.java:1589) ~[?:?]
```

This indicates that we attempt to send the response twice, see:

https://github.com/elastic/elasticsearch/blob/8ec73d6a559d069e18eadc07db493694df196bd4/server/src/main/java/org/elasticsearch/rest/RestController.java#L759-L761

and 

https://github.com/elastic/elasticsearch/blob/8ec73d6a559d069e18eadc07db493694df196bd4/server/src/main/java/org/elasticsearch/rest/RestController.java#L770-L773

This is caused by a race condition in the profiling plugin. It guards with two atomics against duplicate responses but if the method is called after both atomics have just been decremented to zero, we will send the response twice:

https://github.com/elastic/elasticsearch/blob/8ec73d6a559d069e18eadc07db493694df196bd4/x-pack/plugin/profiler/src/main/java/org/elasticsearch/xpack/profiler/TransportGetProfilingAction.java#L422-L429

### Steps to Reproduce

Issue repeated calls against `/_profiling/stacktrace`. As this is a concurrency issue, it may take a few iterations to manifest.

### Logs (if relevant)

_No response_
x-pack/plugin/profiler/src/main/java/org/elasticsearch/xpack/profiler/TransportGetProfilingAction.java
private final GetProfilingResponseBuilder builder;
        private final ActionListener<GetProfilingResponse> submitListener;
        private final Map<String, String> executables;
        private final Map<String, StackFrame> stackFrames;
        private final AtomicInteger expectedExecutableSlices;
        private final AtomicInteger expectedStackFrameSlices;
        private final long start = System.nanoTime();
        private DetailsHandler(
            GetProfilingResponseBuilder builder,
            ActionListener<GetProfilingResponse> submitListener,
            int executableCount,
            int stackFrameCount,
            int expectedExecutableSlices,
            int expectedStackFrameSlices
        ) {
            this.builder = builder;
            this.submitListener = submitListener;
            this.executables = new ConcurrentHashMap<>(executableCount);
            this.stackFrames = new ConcurrentHashMap<>(stackFrameCount);
            this.expectedExecutableSlices = new AtomicInteger(expectedExecutableSlices);
            this.expectedStackFrameSlices = new AtomicInteger(expectedStackFrameSlices);
        }
        public void onStackFramesResponse(MultiGetResponse multiGetItemResponses) {
            for (MultiGetItemResponse frame : multiGetItemResponses) {
                if (frame.isFailed() == false && frame.getResponse().isExists()) {
                    stackFrames.put(frame.getId(), StackFrame.fromSource(frame.getResponse().getSource()));
                }