View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package org.spf4j.base;
33  
34  import com.google.common.annotations.Beta;
35  import edu.umd.cs.findbugs.annotations.CleanupObligation;
36  import edu.umd.cs.findbugs.annotations.DischargesObligation;
37  import java.util.ArrayList;
38  import java.util.Collection;
39  import java.util.Collections;
40  import java.util.List;
41  import java.util.concurrent.TimeUnit;
42  import java.util.concurrent.TimeoutException;
43  import java.util.function.BiFunction;
44  import java.util.function.Consumer;
45  import javax.annotation.Nonnegative;
46  import javax.annotation.Nonnull;
47  import javax.annotation.Nullable;
48  import javax.annotation.ParametersAreNonnullByDefault;
49  import javax.annotation.Signed;
50  import org.spf4j.base.avro.Converters;
51  import org.spf4j.base.avro.DebugDetail;
52  import org.spf4j.base.avro.StackSampleElement;
53  import org.spf4j.log.Level;
54  import org.spf4j.log.Slf4jLogRecord;
55  
56  /**
57   * Execution context information encapsulated a place to store execution related information:
58   * <ul>
59   * <li>deadline/timeout</li>
60   * <li>context logs/overrides</li>
61   * <li>tagged attachments (profiling info, etc..)</li>
62   * </ul>
63   * @author Zoltan Farkas
64   */
65  @CleanupObligation
66  @ParametersAreNonnullByDefault
67  public interface ExecutionContext extends AutoCloseable, JsonWriteable {
68  
69    public interface SimpleTag<T> extends Tag<T, Void> {
70    }
71  
72    public interface Tag<T, A> {
73  
74      String toString();
75  
76      /**
77       * if true, a child execution context will check parent execution contexts
78       * for tag values if not local values exist.
79       */
80      default boolean isInherited(final Relation relation) {
81        return true;
82      }
83  
84      /**
85       * push this tag/values to the parent context when current context is closed.
86       * only child -> parent will be pushed, follows relationships will not be considered.
87       */
88      default boolean pushOnClose() {
89        return false;
90      }
91  
92  
93      default T accumulate(@Nullable final T existing, final T newVal) {
94        return newVal;
95      }
96  
97      default T accumulateComponent(@Nullable final T existing, final A component) {
98        throw new UnsupportedOperationException();
99      }
100 
101   }
102 
103   enum Relation {
104     CHILD_OF, FOLLOWS
105   }
106 
107   @DischargesObligation
108   void close();
109 
110   @Nonnull
111   String getName();
112 
113   CharSequence getId();
114 
115   long getStartTimeNanos();
116 
117   long getDeadlineNanos();
118 
119   @Nullable
120   ExecutionContext getSource();
121 
122   /**
123    * @return the top source. will return this is current is root.
124    * will follow all relationship types.
125    */
126   default ExecutionContext getRoot() {
127     ExecutionContext curr = this;
128     ExecutionContext parent;
129     while ((parent = curr.getSource()) != null) {
130       curr = parent;
131     }
132     return curr;
133   }
134 
135   /**
136    * @return Adam of this current context. will return this is current is Adam.
137    * follows only CHILD_OF relationships.
138    */
139   default ExecutionContext getRootParent() {
140     ExecutionContext curr = this;
141     ExecutionContext parent;
142     while (curr.getRelationToSource() == Relation.CHILD_OF && (parent = curr.getSource()) != null) {
143       curr = parent;
144     }
145     return curr;
146   }
147 
148   /**
149    * @return will return the first not closed parent. null if no parent is available.
150    */
151   @Nullable
152   default ExecutionContext getNotClosedParent() {
153     ExecutionContext curr = this;
154     ExecutionContext parent;
155     do {
156       if (curr.getRelationToSource() != Relation.CHILD_OF) {
157         return null;
158       }
159       parent = curr.getSource();
160       if (parent == null || !parent.isClosed()) {
161         break;
162       }
163       curr = parent;
164     } while (true);
165     return parent;
166   }
167 
168   @Beta
169   void addLog(Slf4jLogRecord log);
170 
171   @Beta
172   void addLogs(Collection<Slf4jLogRecord> log);
173 
174   /**
175    * Attach a AutoCloseable to execution context.
176    * All of them will be closed when context is closed, in reverse registration order.
177    * @param closeable
178    */
179   @Beta
180   void addCloseable(AutoCloseable closeable);
181 
182   /**
183    * The minimum log level accepted by this execution context;
184    * The logs that we will store in this context.
185    * @return
186    */
187   @Beta
188   Level getContextMinLogLevel(String loggerName);
189 
190   default Level getContextMinLogLevel() {
191     return getContextMinLogLevel("");
192   }
193 
194   /**
195    * The minimum log level overwrite.
196    * An execution context can overwrite the backend configured log level.
197    * @return null if not specified.
198    */
199   @Beta
200   @Nullable
201   Level getBackendMinLogLevel(String loggerName);
202 
203   @Nullable
204   default Level getBackendMinLogLevel() {
205     return getBackendMinLogLevel("");
206   }
207 
208   @Beta
209   @Nullable
210   Level setBackendMinLogLevel(String loggerName, Level level);
211 
212   @Nullable
213   default Level setBackendMinLogLevel(Level level) {
214     return setBackendMinLogLevel("", level);
215   }
216 
217   @Beta
218   void streamLogs(Consumer<Slf4jLogRecord> to);
219 
220   @Beta
221   void streamLogs(Consumer<Slf4jLogRecord> to, int nrLogs);
222 
223   /**
224    * Detach this execution context from the current Thread.
225    */
226   void detach();
227 
228   /**
229    * Attach execution context to the current thread.
230    * A thread will typically have a stack of execution contexts attached to it.
231    */
232   void attach();
233 
234   /**
235    * @return true if execution context is attached to a thread or not.
236    */
237   boolean isAttached();
238 
239   @Nonnegative
240   default long getTimeToDeadline(final TimeUnit unit) throws TimeoutException {
241      long result = getTimeRelativeToDeadline(unit);
242      if (result <= 0) {
243        throw new TimeoutException("Deadline exceeded by " + (-result) + ' ' + unit);
244      }
245      return result;
246   }
247 
248   @Nonnegative
249   default long getUncheckedTimeToDeadline(final TimeUnit unit) {
250      long result = getTimeRelativeToDeadline(unit);
251      if (result <= 0) {
252        throw new UncheckedTimeoutException("Deadline exceeded by " + (-result) + ' ' + unit);
253      }
254      return result;
255   }
256 
257   @Signed
258   default long getTimeRelativeToDeadline(final TimeUnit unit)  {
259      return unit.convert(getDeadlineNanos() - TimeSource.nanoTime(), TimeUnit.NANOSECONDS);
260   }
261 
262   @Nonnegative
263   default long getMillisToDeadline() throws TimeoutException {
264     return getTimeToDeadline(TimeUnit.MILLISECONDS);
265   }
266 
267   @Nonnegative
268   default int getSecondsToDeadline() throws TimeoutException {
269     long secondsToDeadline = getTimeToDeadline(TimeUnit.SECONDS);
270     if (secondsToDeadline > Integer.MAX_VALUE) {
271       return Integer.MAX_VALUE;
272     } else {
273       return (int) secondsToDeadline;
274     }
275   }
276 
277   /**
278    * Method to get context associated data.
279    * if current context does not have baggage, the parent context is queried if tag is inherited.
280    * This is done recursively.
281    * @param <T> type of data.
282    * @param key key of data.
283    * @return the data
284    */
285   @Nullable
286   @Beta
287   <T> T get(Tag<T, ?> key);
288 
289 
290   /**
291    * Method to get context associated data.
292    * if current context does not have baggage, the parent context is queried if tag is inherited.
293    * This is done recursively, and both the context and the value are returned.
294    * @param <T> type of data.
295    * @param key key of data.
296    * @return the data
297    */
298   @Nullable
299   @Beta
300   <T> ContextValue<T> getContextAndValue(Tag<T, ?> key);
301 
302   /**
303    * Method to get context associated data.
304    * will ignore inheritance tag attribute.
305    * @param <T> type of data.
306    * @param key key of data.
307    * @return the data
308    */
309   @Nullable
310   @Beta
311   <T> T getLocal(Tag<T, ?> key);
312 
313 
314   /**
315    * Method to put context associated data.
316    * @param <T> type of data.
317    * @param key the key of data.
318    * @param data the data.
319    * @return existing data if there.
320    */
321   @Nullable
322   @Beta
323   <T> T put(Tag<T, ?> tag, T data);
324 
325   /**
326    * @deprecated use accumulate.
327    */
328   @Deprecated
329   default <T> void combine(Tag<T, ?> tag, T data) {
330     accumulate(tag, data);
331   }
332 
333   @Beta
334   default <T> void accumulate(Tag<T, ?> tag, T data) {
335     compute(tag, (t, v) -> t.accumulate(v, data));
336   }
337 
338   @Beta
339   default <T, A> void accumulateComponent(Tag<T, A> tag, A data) {
340     compute(tag, (t, v) -> t.accumulateComponent(v, data));
341   }
342 
343   /**
344    * Method to put context associated data to the root context.
345    * @param <T> type of data.
346    * @param key the key of data.
347    * @param data the data.
348    * @return existing data if there.
349    */
350   @Nullable
351   @Beta
352   default <T> T putToRootParent(final Tag<T, ?> key, final T data) {
353     return getRootParent().put(key, data);
354   }
355 
356   /**
357    * Compute context associated data.
358    * @param <K>
359    * @param <V>
360    * @param key
361    * @param compute
362    * @return
363    */
364   @Beta
365   @Nullable
366   <V, A> V compute(Tag<V, A> key, BiFunction<Tag<V, A>, V, V> compute);
367 
368 
369   @Nullable
370   @Beta
371   default <T> List<T> addToRootParent(final Tag<List<T>, T> tag, final T data) {
372     ExecutionContext ctx = getRootParent();
373     return ctx.compute(tag, (k, v) -> {
374       if (v == null)  {
375         v = new ArrayList<>(2);
376       }
377       v.add(data);
378       return v;
379     });
380   }
381 
382   default ExecutionContext startChild(final String operationName,
383           final long timeout, final TimeUnit tu) {
384     return ExecutionContexts.start(operationName, this, timeout, tu);
385   }
386 
387   default ExecutionContext startChild(final String operationName) {
388     return ExecutionContexts.start(operationName, this);
389   }
390 
391   default ExecutionContext detachedChild(final String operationName,
392           final long timeout, final TimeUnit tu) {
393     return ExecutionContexts.createDetached(operationName, this, timeout, tu);
394   }
395 
396   default ExecutionContext detachedChild(final String operationName) {
397     return ExecutionContexts.createDetached(operationName, this, TimeSource.nanoTime(), this.getDeadlineNanos());
398   }
399 
400   long nextChildId();
401 
402   void add(StackTraceElement[] sample);
403 
404   void add(StackSamples samples);
405 
406   @Nullable
407   StackSamples getAndClearStackSamples();
408 
409   @Nullable
410   StackSamples getStackSamples();
411 
412 
413   boolean isClosed();
414 
415   Relation getRelationToSource();
416 
417 
418   default DebugDetail getDebugDetail(final String origin, @Nullable final Throwable throwable) {
419     return getDebugDetail(origin, throwable, true);
420   }
421 
422   default DebugDetail getDebugDetail(final String origin, @Nullable final Throwable throwable,
423           boolean addStackSamples) {
424     return getDebugDetail(origin, throwable, addStackSamples, 100);
425   }
426 
427   default DebugDetail getDebugDetail(final String origin, @Nullable final Throwable throwable,
428           boolean addStackSamples, final int maxNrLogs) {
429     List<Slf4jLogRecord> ctxLogs = new ArrayList<>();
430     ExecutionContext curr = this;
431     while (curr != null) {
432       curr.streamLogs((log) -> {
433         ctxLogs.add(log);
434       }, maxNrLogs);
435       curr = curr.getSource();
436     }
437     Collections.sort(ctxLogs, Slf4jLogRecord::compareByTimestamp);
438     if (addStackSamples) {
439       StackSamples ss = this.getAndClearStackSamples();
440       List<StackSampleElement> sses = Converters.convert(ss);
441       return new DebugDetail(origin + '/' + this.getName(),
442               Converters.convert("", this.getId().toString(), ctxLogs),
443               throwable == null ? null : Converters.convert(throwable),
444               sses);
445     } else {
446             return new DebugDetail(origin + '/' + this.getName(),
447               Converters.convert("", this.getId().toString(), ctxLogs),
448               throwable == null ? null : Converters.convert(throwable),
449               Collections.emptyList());
450     }
451 
452   }
453 
454 }