1717 * under the License.
1818 */
1919
20- package org .elasticsearch .rest .action . search ;
20+ package org .elasticsearch .rest .action ;
2121
2222import org .elasticsearch .action .ActionListener ;
2323import org .elasticsearch .action .ActionRequest ;
2424import org .elasticsearch .action .ActionResponse ;
2525import org .elasticsearch .action .ActionType ;
2626import org .elasticsearch .action .admin .cluster .node .tasks .cancel .CancelTasksRequest ;
27- import org .elasticsearch .action .admin .cluster .node .tasks .cancel .CancelTasksResponse ;
28- import org .elasticsearch .action .support .ContextPreservingActionListener ;
2927import org .elasticsearch .client .Client ;
28+ import org .elasticsearch .client .FilterClient ;
29+ import org .elasticsearch .client .OriginSettingClient ;
3030import org .elasticsearch .client .node .NodeClient ;
31- import org .elasticsearch .common .util .concurrent .ThreadContext ;
3231import org .elasticsearch .http .HttpChannel ;
3332import org .elasticsearch .tasks .Task ;
3433import org .elasticsearch .tasks .TaskId ;
3534
35+ import java .util .ArrayList ;
3636import java .util .HashSet ;
37+ import java .util .List ;
3738import java .util .Map ;
3839import java .util .Set ;
3940import java .util .concurrent .ConcurrentHashMap ;
4041import java .util .concurrent .atomic .AtomicReference ;
4142
43+ import static org .elasticsearch .action .admin .cluster .node .tasks .get .GetTaskAction .TASKS_ORIGIN ;
44+
4245/**
43- * This class executes a request and associates the corresponding {@link Task} with the {@link HttpChannel} that it was originated from,
44- * so that the tasks associated with a certain channel get cancelled when the underlying connection gets closed .
46+ * A {@linkplain Client} that cancels tasks executed locally when the provided {@link HttpChannel}
47+ * is closed before completion .
4548 */
46- public final class HttpChannelTaskHandler {
49+ public class RestCancellableNodeClient extends FilterClient {
50+ private static final Map <HttpChannel , CloseListener > httpChannels = new ConcurrentHashMap <>();
51+
52+ private final NodeClient client ;
53+ private final HttpChannel httpChannel ;
4754
48- public static final HttpChannelTaskHandler INSTANCE = new HttpChannelTaskHandler ();
49- //package private for testing
50- final Map <HttpChannel , CloseListener > httpChannels = new ConcurrentHashMap <>();
55+ public RestCancellableNodeClient (NodeClient client , HttpChannel httpChannel ) {
56+ super (client );
57+ this .client = client ;
58+ this .httpChannel = httpChannel ;
59+ }
60+
61+ /**
62+ * Returns the number of channels tracked globally.
63+ */
64+ public static int getNumChannels () {
65+ return httpChannels .size ();
66+ }
5167
52- private HttpChannelTaskHandler () {
68+ /**
69+ * Returns the number of tasks tracked globally.
70+ */
71+ static int getNumTasks () {
72+ return httpChannels .values ().stream ()
73+ .mapToInt (CloseListener ::getNumTasks )
74+ .sum ();
5375 }
5476
55- <Response extends ActionResponse > void execute (NodeClient client , HttpChannel httpChannel , ActionRequest request ,
56- ActionType <Response > actionType , ActionListener <Response > listener ) {
77+ /**
78+ * Returns the number of tasks tracked by the provided {@link HttpChannel}.
79+ */
80+ static int getNumTasks (HttpChannel channel ) {
81+ CloseListener listener = httpChannels .get (channel );
82+ return listener == null ? 0 : listener .getNumTasks ();
83+ }
5784
58- CloseListener closeListener = httpChannels .computeIfAbsent (httpChannel , channel -> new CloseListener (client ));
85+ @ Override
86+ public <Request extends ActionRequest , Response extends ActionResponse > void doExecute (
87+ ActionType <Response > action , Request request , ActionListener <Response > listener ) {
88+ CloseListener closeListener = httpChannels .computeIfAbsent (httpChannel , channel -> new CloseListener ());
5989 TaskHolder taskHolder = new TaskHolder ();
60- Task task = client .executeLocally (actionType , request ,
90+ Task task = client .executeLocally (action , request ,
6191 new ActionListener <>() {
6292 @ Override
63- public void onResponse (Response searchResponse ) {
93+ public void onResponse (Response response ) {
6494 try {
6595 closeListener .unregisterTask (taskHolder );
6696 } finally {
67- listener .onResponse (searchResponse );
97+ listener .onResponse (response );
6898 }
6999 }
70100
@@ -77,32 +107,35 @@ public void onFailure(Exception e) {
77107 }
78108 }
79109 });
80- closeListener .registerTask (taskHolder , new TaskId (client .getLocalNodeId (), task .getId ()));
110+ final TaskId taskId = new TaskId (client .getLocalNodeId (), task .getId ());
111+ closeListener .registerTask (taskHolder , taskId );
81112 closeListener .maybeRegisterChannel (httpChannel );
82113 }
83114
84- public int getNumChannels () {
85- return httpChannels .size ();
115+ private void cancelTask (TaskId taskId ) {
116+ CancelTasksRequest req = new CancelTasksRequest ()
117+ .setTaskId (taskId )
118+ .setReason ("channel closed" );
119+ // force the origin to execute the cancellation as a system user
120+ new OriginSettingClient (client , TASKS_ORIGIN ).admin ().cluster ().cancelTasks (req , ActionListener .wrap (() -> {}));
86121 }
87122
88- final class CloseListener implements ActionListener <Void > {
89- private final Client client ;
123+ private class CloseListener implements ActionListener <Void > {
90124 private final AtomicReference <HttpChannel > channel = new AtomicReference <>();
91- private final Set <TaskId > taskIds = new HashSet <>();
125+ private final Set <TaskId > tasks = new HashSet <>();
92126
93- CloseListener (Client client ) {
94- this .client = client ;
127+ CloseListener () {
95128 }
96129
97- int getNumTasks () {
98- return taskIds .size ();
130+ synchronized int getNumTasks () {
131+ return tasks .size ();
99132 }
100133
101134 void maybeRegisterChannel (HttpChannel httpChannel ) {
102135 if (channel .compareAndSet (null , httpChannel )) {
103136 //In case the channel is already closed when we register the listener, the listener will be immediately executed which will
104137 //remove the channel from the map straight-away. That is why we first create the CloseListener and later we associate it
105- //with the channel. This guarantees that the close listener is already in the map when the it gets registered to its
138+ //with the channel. This guarantees that the close listener is already in the map when it gets registered to its
106139 //corresponding channel, hence it is always found in the map when it gets invoked if the channel gets closed.
107140 httpChannel .addCloseListener (this );
108141 }
@@ -111,34 +144,31 @@ void maybeRegisterChannel(HttpChannel httpChannel) {
111144 synchronized void registerTask (TaskHolder taskHolder , TaskId taskId ) {
112145 taskHolder .taskId = taskId ;
113146 if (taskHolder .completed == false ) {
114- this .taskIds .add (taskId );
147+ this .tasks .add (taskId );
115148 }
116149 }
117150
118151 synchronized void unregisterTask (TaskHolder taskHolder ) {
119152 if (taskHolder .taskId != null ) {
120- this .taskIds .remove (taskHolder .taskId );
153+ this .tasks .remove (taskHolder .taskId );
121154 }
122155 taskHolder .completed = true ;
123156 }
124157
125158 @ Override
126- public synchronized void onResponse (Void aVoid ) {
127- //When the channel gets closed it won't be reused: we can remove it from the map and forget about it.
128- CloseListener closeListener = httpChannels .remove (channel .get ());
159+ public void onResponse (Void aVoid ) {
160+ final HttpChannel httpChannel = channel .get ();
161+ assert httpChannel != null : "channel not registered" ;
162+ // when the channel gets closed it won't be reused: we can remove it from the map and forget about it.
163+ CloseListener closeListener = httpChannels .remove (httpChannel );
129164 assert closeListener != null : "channel not found in the map of tracked channels" ;
130- for (TaskId taskId : taskIds ) {
131- ThreadContext threadContext = client .threadPool ().getThreadContext ();
132- try (ThreadContext .StoredContext ignore = threadContext .stashContext ()) {
133- // we stash any context here since this is an internal execution and should not leak any existing context information
134- threadContext .markAsSystemContext ();
135- ContextPreservingActionListener <CancelTasksResponse > contextPreservingListener = new ContextPreservingActionListener <>(
136- threadContext .newRestorableContext (false ), ActionListener .wrap (r -> {}, e -> {}));
137- CancelTasksRequest cancelTasksRequest = new CancelTasksRequest ();
138- cancelTasksRequest .setTaskId (taskId );
139- //We don't wait for cancel tasks to come back. Task cancellation is just best effort.
140- client .admin ().cluster ().cancelTasks (cancelTasksRequest , contextPreservingListener );
141- }
165+ final List <TaskId > toCancel ;
166+ synchronized (this ) {
167+ toCancel = new ArrayList <>(tasks );
168+ tasks .clear ();
169+ }
170+ for (TaskId taskId : toCancel ) {
171+ cancelTask (taskId );
142172 }
143173 }
144174
0 commit comments