2012-01-10 22:11:51 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
#include <list>
|
|
|
|
|
#include <queue>
|
|
|
|
|
|
|
|
|
|
#include <Poco/Thread.h>
|
|
|
|
|
#include <Poco/Mutex.h>
|
2012-01-10 22:11:51 +00:00
|
|
|
|
#include <Poco/Semaphore.h>
|
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
#include <Yandex/logger_useful.h>
|
2012-01-10 22:11:51 +00:00
|
|
|
|
|
|
|
|
|
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
using Poco::SharedPtr;
|
|
|
|
|
|
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
/** Очень простая thread-safe очередь ограниченной длины.
|
|
|
|
|
* Если пытаться вынуть элемент из пустой очереди, то поток блокируется, пока очередь не станет непустой.
|
|
|
|
|
* Если пытаться вставить элемент в переполненную очередь, то поток блокируется, пока в очереди не появится элемент.
|
|
|
|
|
*/
|
|
|
|
|
template <typename T>
|
|
|
|
|
class ConcurrentBoundedQueue
|
|
|
|
|
{
|
|
|
|
|
private:
|
|
|
|
|
size_t max_fill;
|
|
|
|
|
std::queue<T> queue;
|
|
|
|
|
Poco::Mutex mutex;
|
|
|
|
|
Poco::Semaphore fill_count;
|
|
|
|
|
Poco::Semaphore empty_count;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
ConcurrentBoundedQueue(size_t max_fill)
|
|
|
|
|
: fill_count(0, max_fill), empty_count(max_fill, max_fill) {}
|
|
|
|
|
|
|
|
|
|
void push(const T & x)
|
|
|
|
|
{
|
|
|
|
|
empty_count.wait();
|
|
|
|
|
{
|
|
|
|
|
Poco::ScopedLock<Poco::Mutex> lock(mutex);
|
|
|
|
|
queue.push(x);
|
|
|
|
|
}
|
|
|
|
|
fill_count.set();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pop(T & x)
|
|
|
|
|
{
|
|
|
|
|
fill_count.wait();
|
|
|
|
|
{
|
|
|
|
|
Poco::ScopedLock<Poco::Mutex> lock(mutex);
|
|
|
|
|
x = queue.front();
|
|
|
|
|
queue.pop();
|
|
|
|
|
}
|
|
|
|
|
empty_count.set();
|
|
|
|
|
}
|
2012-10-20 23:02:13 +00:00
|
|
|
|
|
2012-11-10 05:20:56 +00:00
|
|
|
|
bool tryPush(const T & x)
|
|
|
|
|
{
|
|
|
|
|
if (empty_count.tryWait(0))
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
Poco::ScopedLock<Poco::Mutex> lock(mutex);
|
|
|
|
|
queue.push(x);
|
|
|
|
|
}
|
|
|
|
|
fill_count.set();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool tryPop(T & x)
|
|
|
|
|
{
|
|
|
|
|
if (fill_count.tryWait(0))
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
Poco::ScopedLock<Poco::Mutex> lock(mutex);
|
|
|
|
|
x = queue.front();
|
|
|
|
|
queue.pop();
|
|
|
|
|
}
|
|
|
|
|
empty_count.set();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-20 23:02:13 +00:00
|
|
|
|
void clear()
|
|
|
|
|
{
|
|
|
|
|
while (fill_count.tryWait(0))
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
Poco::ScopedLock<Poco::Mutex> lock(mutex);
|
|
|
|
|
queue.pop();
|
|
|
|
|
}
|
|
|
|
|
empty_count.set();
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-09-24 02:05:40 +00:00
|
|
|
|
};
|
|
|
|
|
|
2012-09-03 19:52:57 +00:00
|
|
|
|
|
2012-01-10 22:11:51 +00:00
|
|
|
|
/** Объединяет несколько источников в один.
|
|
|
|
|
* Блоки из разных источников перемежаются друг с другом произвольным образом.
|
|
|
|
|
* Можно указать количество потоков (max_threads),
|
|
|
|
|
* в которых будет выполняться получение данных из разных источников.
|
2012-09-24 02:05:40 +00:00
|
|
|
|
*
|
|
|
|
|
* Устроено так:
|
|
|
|
|
* - есть набор источников, из которых можно вынимать блоки;
|
|
|
|
|
* - есть набор потоков, которые могут одновременно вынимать блоки из разных источников;
|
|
|
|
|
* - "свободные" источники (с которыми сейчас не работает никакой поток) кладутся в очередь источников;
|
|
|
|
|
* - когда поток берёт источник для обработки, он удаляет его из очереди источников,
|
|
|
|
|
* вынимает из него блок, и затем кладёт источник обратно в очередь источников;
|
|
|
|
|
* - полученные блоки складываются в ограниченную очередь готовых блоков;
|
|
|
|
|
* - основной поток вынимает готовые блоки из очереди готовых блоков.
|
2012-01-10 22:11:51 +00:00
|
|
|
|
*/
|
|
|
|
|
class UnionBlockInputStream : public IProfilingBlockInputStream
|
|
|
|
|
{
|
2012-09-04 19:57:17 +00:00
|
|
|
|
class Thread;
|
2012-01-10 22:11:51 +00:00
|
|
|
|
public:
|
|
|
|
|
UnionBlockInputStream(BlockInputStreams inputs_, unsigned max_threads_ = 1)
|
|
|
|
|
: max_threads(std::min(inputs_.size(), static_cast<size_t>(max_threads_))),
|
2012-11-10 04:42:26 +00:00
|
|
|
|
output_queue(max_threads), exhausted_inputs(0), finish(false), all_read(false),
|
2012-09-24 02:05:40 +00:00
|
|
|
|
log(&Logger::get("UnionBlockInputStream"))
|
2012-01-10 22:11:51 +00:00
|
|
|
|
{
|
|
|
|
|
children.insert(children.end(), inputs_.begin(), inputs_.end());
|
2012-06-24 23:17:06 +00:00
|
|
|
|
|
2012-01-10 22:11:51 +00:00
|
|
|
|
for (size_t i = 0; i < inputs_.size(); ++i)
|
2012-06-24 23:17:06 +00:00
|
|
|
|
{
|
2012-09-24 02:05:40 +00:00
|
|
|
|
input_queue.push(InputData());
|
|
|
|
|
input_queue.back().in = inputs_[i];
|
|
|
|
|
input_queue.back().i = i;
|
2012-06-24 23:17:06 +00:00
|
|
|
|
}
|
2012-01-10 22:11:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-10-20 02:10:47 +00:00
|
|
|
|
String getName() const { return "UnionBlockInputStream"; }
|
|
|
|
|
|
|
|
|
|
BlockInputStreamPtr clone() { return new UnionBlockInputStream(children, max_threads); }
|
|
|
|
|
|
|
|
|
|
~UnionBlockInputStream()
|
|
|
|
|
{
|
|
|
|
|
LOG_TRACE(log, "Waiting for threads to finish");
|
|
|
|
|
|
|
|
|
|
finish = true;
|
|
|
|
|
cancel();
|
2012-10-20 06:40:55 +00:00
|
|
|
|
|
2012-12-06 17:32:48 +00:00
|
|
|
|
ExceptionPtr exception;
|
|
|
|
|
|
2012-10-20 23:02:13 +00:00
|
|
|
|
/// Вынем всё, что есть в очереди готовых данных.
|
2012-11-10 05:20:56 +00:00
|
|
|
|
OutputData res;
|
|
|
|
|
while (output_queue.tryPop(res))
|
2012-12-06 17:32:48 +00:00
|
|
|
|
if (res.exception && !exception)
|
|
|
|
|
exception = res.exception;
|
2012-10-20 23:02:13 +00:00
|
|
|
|
|
|
|
|
|
/** В этот момент, запоздавшие потоки ещё могут вставить в очередь какие-нибудь блоки, но очередь не переполнится.
|
|
|
|
|
* PS. Может быть, для переменной finish нужен барьер?
|
|
|
|
|
*/
|
|
|
|
|
|
2012-10-20 02:10:47 +00:00
|
|
|
|
for (ThreadsData::iterator it = threads_data.begin(); it != threads_data.end(); ++it)
|
|
|
|
|
it->thread->join();
|
|
|
|
|
|
2012-11-10 05:20:56 +00:00
|
|
|
|
/// Может быть, нам под конец положили эксепшен.
|
|
|
|
|
while (output_queue.tryPop(res))
|
2012-12-06 17:32:48 +00:00
|
|
|
|
if (res.exception && !exception)
|
|
|
|
|
exception = res.exception;
|
|
|
|
|
|
|
|
|
|
if (exception && !std::uncaught_exception())
|
|
|
|
|
exception->rethrow();
|
2012-11-10 05:20:56 +00:00
|
|
|
|
|
2012-10-20 02:10:47 +00:00
|
|
|
|
LOG_TRACE(log, "Waited for threads to finish");
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-30 19:17:41 +00:00
|
|
|
|
/** Отличается от реализации по-умолчанию тем, что пытается остановить все источники,
|
|
|
|
|
* пропуская отвалившиеся по эксепшену.
|
|
|
|
|
*/
|
|
|
|
|
void cancel()
|
|
|
|
|
{
|
2012-11-10 05:13:46 +00:00
|
|
|
|
if (!__sync_bool_compare_and_swap(&is_cancelled, false, true))
|
|
|
|
|
return;
|
2012-11-10 04:42:26 +00:00
|
|
|
|
|
2012-10-30 19:17:41 +00:00
|
|
|
|
ExceptionPtr exception;
|
|
|
|
|
for (BlockInputStreams::iterator it = children.begin(); it != children.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
if (IProfilingBlockInputStream * child = dynamic_cast<IProfilingBlockInputStream *>(&**it))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
child->cancel();
|
|
|
|
|
}
|
|
|
|
|
catch (const Exception & e)
|
|
|
|
|
{
|
|
|
|
|
if (!exception)
|
|
|
|
|
exception = e.clone();
|
|
|
|
|
}
|
|
|
|
|
catch (const Poco::Exception & e)
|
|
|
|
|
{
|
|
|
|
|
if (!exception)
|
|
|
|
|
exception = e.clone();
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception & e)
|
|
|
|
|
{
|
|
|
|
|
if (!exception)
|
|
|
|
|
exception = new Exception(e.what(), ErrorCodes::STD_EXCEPTION);
|
|
|
|
|
}
|
|
|
|
|
catch (...)
|
|
|
|
|
{
|
|
|
|
|
if (!exception)
|
|
|
|
|
exception = new Exception("Unknown exception", ErrorCodes::UNKNOWN_EXCEPTION);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (exception)
|
|
|
|
|
exception->rethrow();
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-20 02:10:47 +00:00
|
|
|
|
protected:
|
2012-01-10 22:11:51 +00:00
|
|
|
|
Block readImpl()
|
|
|
|
|
{
|
2012-10-12 16:53:45 +00:00
|
|
|
|
OutputData res;
|
2012-11-10 04:42:26 +00:00
|
|
|
|
if (all_read)
|
2012-10-12 16:53:45 +00:00
|
|
|
|
return res.block;
|
2012-10-20 02:10:47 +00:00
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
/// Запускаем потоки, если это ещё не было сделано.
|
|
|
|
|
if (threads_data.empty())
|
2012-01-10 22:11:51 +00:00
|
|
|
|
{
|
2012-09-24 02:05:40 +00:00
|
|
|
|
threads_data.resize(max_threads);
|
|
|
|
|
for (ThreadsData::iterator it = threads_data.begin(); it != threads_data.end(); ++it)
|
2012-06-22 18:27:40 +00:00
|
|
|
|
{
|
2012-10-12 16:53:45 +00:00
|
|
|
|
it->runnable = new Thread(*this);
|
2012-09-24 02:05:40 +00:00
|
|
|
|
it->thread = new Poco::Thread;
|
|
|
|
|
it->thread->start(*it->runnable);
|
2012-01-10 22:11:51 +00:00
|
|
|
|
}
|
2012-09-24 02:05:40 +00:00
|
|
|
|
}
|
2012-06-24 23:17:06 +00:00
|
|
|
|
|
2012-10-12 16:53:45 +00:00
|
|
|
|
/// Будем ждать, пока будет готов следующий блок или будет выкинуто исключение.
|
2012-09-24 02:05:40 +00:00
|
|
|
|
output_queue.pop(res);
|
2012-01-10 22:11:51 +00:00
|
|
|
|
|
2012-10-12 16:53:45 +00:00
|
|
|
|
if (res.exception)
|
|
|
|
|
res.exception->rethrow();
|
|
|
|
|
|
2012-11-10 04:42:26 +00:00
|
|
|
|
if (!res.block)
|
|
|
|
|
all_read = true;
|
|
|
|
|
|
2012-10-12 16:53:45 +00:00
|
|
|
|
return res.block;
|
2012-01-10 22:11:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
private:
|
2012-01-10 22:11:51 +00:00
|
|
|
|
/// Данные отдельного источника
|
2012-09-04 19:57:17 +00:00
|
|
|
|
struct InputData
|
2012-01-10 22:11:51 +00:00
|
|
|
|
{
|
|
|
|
|
BlockInputStreamPtr in;
|
2012-09-24 02:05:40 +00:00
|
|
|
|
size_t i; /// Порядковый номер источника (для отладки).
|
|
|
|
|
};
|
2012-01-10 22:11:51 +00:00
|
|
|
|
|
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
/// Данные отдельного потока
|
|
|
|
|
struct ThreadData
|
|
|
|
|
{
|
|
|
|
|
SharedPtr<Poco::Thread> thread;
|
|
|
|
|
SharedPtr<Thread> runnable;
|
2012-01-10 22:11:51 +00:00
|
|
|
|
};
|
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
|
2012-09-04 19:57:17 +00:00
|
|
|
|
class Thread : public Poco::Runnable
|
2012-01-10 22:11:51 +00:00
|
|
|
|
{
|
2012-09-04 19:57:17 +00:00
|
|
|
|
public:
|
2012-10-12 16:53:45 +00:00
|
|
|
|
Thread(UnionBlockInputStream & parent_) : parent(parent_) {}
|
2012-09-04 19:57:17 +00:00
|
|
|
|
|
|
|
|
|
void run()
|
2012-01-10 22:11:51 +00:00
|
|
|
|
{
|
2012-10-12 16:53:45 +00:00
|
|
|
|
ExceptionPtr exception;
|
|
|
|
|
|
2012-09-04 19:57:17 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2012-09-24 02:05:40 +00:00
|
|
|
|
loop();
|
2012-09-04 19:57:17 +00:00
|
|
|
|
}
|
|
|
|
|
catch (const Exception & e)
|
2012-01-10 22:11:51 +00:00
|
|
|
|
{
|
2012-09-24 02:05:40 +00:00
|
|
|
|
exception = e.clone();
|
2012-01-10 22:11:51 +00:00
|
|
|
|
}
|
2012-09-04 19:57:17 +00:00
|
|
|
|
catch (const Poco::Exception & e)
|
|
|
|
|
{
|
2012-09-24 02:05:40 +00:00
|
|
|
|
exception = e.clone();
|
2012-09-04 19:57:17 +00:00
|
|
|
|
}
|
|
|
|
|
catch (const std::exception & e)
|
|
|
|
|
{
|
2012-09-24 02:05:40 +00:00
|
|
|
|
exception = new Exception(e.what(), ErrorCodes::STD_EXCEPTION);
|
2012-09-04 19:57:17 +00:00
|
|
|
|
}
|
|
|
|
|
catch (...)
|
|
|
|
|
{
|
2012-09-24 02:05:40 +00:00
|
|
|
|
exception = new Exception("Unknown exception", ErrorCodes::UNKNOWN_EXCEPTION);
|
2012-09-04 19:57:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
if (exception)
|
|
|
|
|
{
|
|
|
|
|
/// Попросим остальные потоки побыстрее прекратить работу.
|
|
|
|
|
parent.finish = true;
|
2012-10-30 19:17:41 +00:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
parent.cancel();
|
|
|
|
|
}
|
|
|
|
|
catch (...)
|
|
|
|
|
{
|
|
|
|
|
/** Если не удалось попросить остановиться одного или несколько источников.
|
|
|
|
|
* (например, разорвано соединение при распределённой обработке запроса)
|
|
|
|
|
* - то пофиг.
|
|
|
|
|
*/
|
|
|
|
|
}
|
2012-09-24 02:05:40 +00:00
|
|
|
|
|
2012-10-12 16:53:45 +00:00
|
|
|
|
/// Отдаём эксепшен в основной поток.
|
|
|
|
|
parent.output_queue.push(exception);
|
2012-09-24 02:05:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void loop()
|
|
|
|
|
{
|
|
|
|
|
while (!parent.finish) /// Может потребоваться прекратить работу раньше, чем все источники иссякнут.
|
|
|
|
|
{
|
|
|
|
|
InputData input;
|
|
|
|
|
|
|
|
|
|
/// Выбираем следующий источник.
|
|
|
|
|
{
|
|
|
|
|
Poco::ScopedLock<Poco::FastMutex> lock(parent.mutex);
|
|
|
|
|
|
|
|
|
|
/// Если свободных источников нет, то этот поток больше не нужен. (Но другие потоки могут работать со своими источниками.)
|
|
|
|
|
if (parent.input_queue.empty())
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
input = parent.input_queue.front();
|
|
|
|
|
|
|
|
|
|
/// Убираем источник из очереди доступных источников.
|
|
|
|
|
parent.input_queue.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Основная работа.
|
|
|
|
|
Block block = input.in->read();
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
Poco::ScopedLock<Poco::FastMutex> lock(parent.mutex);
|
|
|
|
|
|
|
|
|
|
/// Если этот источник ещё не иссяк, то положим полученный блок в очередь готовых.
|
|
|
|
|
if (block)
|
|
|
|
|
{
|
|
|
|
|
parent.input_queue.push(input);
|
|
|
|
|
parent.output_queue.push(block);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
++parent.exhausted_inputs;
|
|
|
|
|
|
|
|
|
|
/// Если все источники иссякли.
|
|
|
|
|
if (parent.exhausted_inputs == parent.children.size())
|
|
|
|
|
{
|
|
|
|
|
/// Отдаём в основной поток пустой блок, что означает, что данных больше нет.
|
2012-10-12 16:53:45 +00:00
|
|
|
|
parent.output_queue.push(OutputData());
|
2012-09-24 02:05:40 +00:00
|
|
|
|
parent.finish = true;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-10 22:11:51 +00:00
|
|
|
|
}
|
2012-09-03 19:52:57 +00:00
|
|
|
|
|
2012-09-04 19:57:17 +00:00
|
|
|
|
private:
|
|
|
|
|
UnionBlockInputStream & parent;
|
|
|
|
|
};
|
|
|
|
|
|
2012-09-24 02:05:40 +00:00
|
|
|
|
|
|
|
|
|
unsigned max_threads;
|
|
|
|
|
|
|
|
|
|
/// Потоки.
|
|
|
|
|
typedef std::list<ThreadData> ThreadsData;
|
|
|
|
|
ThreadsData threads_data;
|
|
|
|
|
|
|
|
|
|
/// Очередь доступных источников, которые не заняты каким-либо потоком в данный момент.
|
|
|
|
|
typedef std::queue<InputData> InputQueue;
|
|
|
|
|
InputQueue input_queue;
|
|
|
|
|
|
2012-10-12 16:53:45 +00:00
|
|
|
|
/// Блок или эксепшен.
|
|
|
|
|
struct OutputData
|
|
|
|
|
{
|
|
|
|
|
Block block;
|
|
|
|
|
ExceptionPtr exception;
|
|
|
|
|
|
|
|
|
|
OutputData() {}
|
|
|
|
|
OutputData(Block & block_) : block(block_) {}
|
|
|
|
|
OutputData(ExceptionPtr & exception_) : exception(exception_) {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Очередь готовых блоков. Также туда можно положить эксепшен вместо блока.
|
|
|
|
|
typedef ConcurrentBoundedQueue<OutputData> OutputQueue;
|
2012-09-24 02:05:40 +00:00
|
|
|
|
OutputQueue output_queue;
|
|
|
|
|
|
|
|
|
|
/// Для операций с очередями.
|
2012-09-04 19:57:17 +00:00
|
|
|
|
Poco::FastMutex mutex;
|
2012-09-24 02:05:40 +00:00
|
|
|
|
|
|
|
|
|
/// Сколько источников иссякло.
|
|
|
|
|
size_t exhausted_inputs;
|
|
|
|
|
|
|
|
|
|
/// Завершить работу потоков (раньше, чем иссякнут источники).
|
|
|
|
|
volatile bool finish;
|
2012-11-10 04:42:26 +00:00
|
|
|
|
bool all_read;
|
2012-09-24 02:05:40 +00:00
|
|
|
|
|
|
|
|
|
Logger * log;
|
2012-01-10 22:11:51 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|