-
Notifications
You must be signed in to change notification settings - Fork 2
Port ActiveSpanSource implementation from opentracing-python. #3
base: master
Are you sure you want to change the base?
Conversation
How does this work with async code? E.g. event loops like ReactPHP or AmPHP, Promises, coroutines, RxPHP, |
@felixfbecker For ReachtPHP/AmPHP i cant say, since i don't know how they work internally. For coroutines, curl_multi* aand mysqli_reap_async_query, the pattern is to still use manual management after obtaining the parent active span: <?php
$parent = $tracer->getActiveSpan()->getContext();
$ch1 = curl_init();
$ch2 = curl_init();
// URL und weitere Optionen setzen
curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/");
curl_setopt($ch2, CURLOPT_HEADER, 0);
$mh = curl_multi_init();
curl_multi_add_handle($mh,$ch1);
$ch1Span = $tracer->startManualSpan('http', ['child_of' => $parent]);
curl_multi_add_handle($mh,$ch2);
$ch2Span = $tracer->startManualSpan('http', ['child_of' => $parent]);
$active = null;
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
curl_multi_remove_handle($mh, $ch1);
$ch1Span->finish();
curl_multi_remove_handle($mh, $ch2);
$ch2Span->finish();
curl_multi_close($mh); Depending on the implementation of ActiveSpanSource, it might be possible to do this with ReactPHP by maintaining multiple stacks of spans (for every execution context) and whenever the context switches to switch the stack as well. |
I am concerned that if we add this to the official OpenTracing implementation, then all PHP libraries will just use it without thinking about all these cases. If you want to add this to the implementation, the usage implications must be documented and how to support async patterns. If these async patterns are not supported, then the active span container will do more harm than good. Passing spans as parameters on the other hand always works, no matter if async or not. |
@felixfbecker this is already added to the official java library, and you can see the PR for the python implementation. This is becoming part of the specification and we need to support it. Libraries must be written specifically for ReactPHP anyways, because PHP libraries usually block on I/O, that means if they are to support OpenTracing then they can implement this handling right there. And another argument, how would third libraries even support OpenTracing at the moment? Its not possible, because there is no way to access the current context from a generic third party library. |
You can pass spans as parameters, just like you do in JS (and Go with |
@felixfbecker I agree on passing context as arugment in async/reactive environments, the problem is none of the libraries support this at their core level, which is a requirement to make this work. So you must hook into the event loops at different abstractions. For ReactPHP lets take this example from https://blog.wyrihaximus.net/2015/04/reactphp-http/:
It has 4 executions, the request itself, and 2 then() callbacks doing filesystem I/O and one writing the response. If you where to instrument this using OpenTracing, then the tracing code must be wrapped in decorators around the 4 closures that are executed on, and 3x then. They would need to know "their" request context and modify the ActiveSpanSource accordingly. This is highly dependent code on the async framework, and same as opentracing-python, where there are cusotm span source examples for every async framework: See this example for asyncio with code that hooks into the event loop at a very low levle: https://github.com/palazzem/ot-examples/blob/master/examples/asyncio.py |
@felixfbecker Actually for ReactPHP it should (untested code) work with a loop decorator like this: <?php
class TracerLoopDecorator implements LoopInterface
{
private $parentLoop;
private $tracer;
public function __construct(LoopInterface $parent, Tracer $tracer)
{
$this->parentLoop = $parent;
$this->tracer = $tracer;
$this->activeSpanSource = $tracer->getActiveSpanSource();
}
public function addReadStream($stream, callable $listener)
{
// is called in the context of a current span
$span = $this->tracer->getActiveSpan();
$wrapper = function (...$args) use ($span, $listener) {
$this->activeSpanSource->activate($span);
$result = $listener(...$args);
$this->activeSpanSource->deactivate($span);
return $result;
};
return $this->parentLoop->addReadStream($stream, $wrapper);
}
// ....
} |
The implementation of active span source and related code is an almost 1:1 port of the current python pull request: opentracing/opentracing-python#52
The specification context is "In-process propagation" discussed in this issue opentracing/specification#23 and the first official implementation in open-tracing java, opentracing/opentracing-java#115
It provides the following semantics:
Tracer::getActiveSpan()
returns the currently active span.Tracer::startActiveSpan()
creates a new span with the currently active span as parent.Span::finish()
on an actively managed span, automatically deactivates the Span usingActiveSpanSource::deactivate()
.Having this feature in before making an official version of opentracing-php will safe us from the BC Break.
/cc @jcchavezs @felixfbecker