Skip to content

Commit 291397f

Browse files
ricklavoiefacebook-github-bot
authored andcommitted
Introduce extern-worker framework (#9025)
Summary: Pull Request resolved: #9025 Introduce the new extern-worker framework. This is a framework which allows you run work out of process. You "upload" data into the framework and receive an abstract "Ref" to the data. You can then invoke various "jobs" with those Refs as inputs,and receive more Refs as outputs. This lets you pipe data from one job to the next without needing to have it be present locally. It supports different "implementations" which can store the data and execute the jobs different ways. The implementation which is always available stores data on disk and uses fork+exec to execute the jobs. Most of the framework is agnostic to the implementation, keeping the logic an implementation needs to implement to a minimum. For extra reliability, the framework supports "fallbacks". Depending on configuration, if executing a job using the normal implementation fails, we can retry the job using the "fallback" implementation, which is always the fork+exec implementation. This (might be) slower, but is considered more reliable, and is designed to prevent spurious failures from stopping execution. This fallback is invisible to the user of the framework. For the most part, jobs can be written as normal functions within a C++ class. They receive/return normal C++ types and don't have to worry about serialization or how the data is transported around. The only requirement is that the types must be usable with BlobEncoder/BlobDecoder. Implementations besides the builtin fork+exec one can be provided by a creation hook. This hook can be overwritten and used to register a creation function. So, additional implementations can be separate from the framework proper. Reviewed By: mofarrell Differential Revision: D34710025 fbshipit-source-id: 7b180bad60210c4814a33f813772ccb2c0d01b31
1 parent 226cb63 commit 291397f

8 files changed

+4206
-0
lines changed

hphp/hhvm/main.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "hphp/util/embedded-data.h"
2828
#include "hphp/util/embedded-vfs.h"
29+
#include "hphp/util/extern-worker.h"
2930
#include "hphp/util/logger.h"
3031
#include "hphp/util/process.h"
3132
#include "hphp/util/process-exec.h"
@@ -78,6 +79,10 @@ int main(int argc, char** argv) {
7879
return HPHP::HHBBC::main(argc - 1, argv + 1);
7980
}
8081

82+
if (argc > 1 && !strcmp(argv[1], HPHP::extern_worker::s_option)) {
83+
return HPHP::extern_worker::main(argc, argv);
84+
}
85+
8186
HPHP::register_process_init();
8287
if (len >= 3 && !strcmp(argv[0] + len - 3, "php")) {
8388
return HPHP::emulate_zend(argc, argv);

hphp/util/extern-worker-detail.h

+323
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| HipHop for PHP |
4+
+----------------------------------------------------------------------+
5+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6+
+----------------------------------------------------------------------+
7+
| This source file is subject to version 3.01 of the PHP license, |
8+
| that is bundled with this package in the file LICENSE, and is |
9+
| available through the world-wide-web at the following url: |
10+
| http://www.php.net/license/3_01.txt |
11+
| If you did not receive a copy of the PHP license and are unable to |
12+
| obtain it through the world-wide-web, please send a note to |
13+
| license@php.net so we can mail you a copy immediately. |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifndef incl_HPHP_EXTERN_WORKER_DETAIL_H_
18+
#error "extern-worker-detail.h should only be included by extern-worker.h"
19+
#endif
20+
21+
#include "hphp/util/assertions.h"
22+
#include "hphp/util/blob-encoder.h"
23+
24+
#include <folly/String.h>
25+
#include <folly/portability/Filesystem.h>
26+
27+
#include <chrono>
28+
#include <string>
29+
#include <tuple>
30+
#include <type_traits>
31+
#include <vector>
32+
33+
/*
34+
* Implementation details for extern_worker that don't need to be
35+
* exposed to users.
36+
*/
37+
38+
namespace HPHP::extern_worker {
39+
40+
//////////////////////////////////////////////////////////////////////
41+
42+
// This header gets included before any others, so these need to be
43+
// forward declared.
44+
template <typename T> struct Opt;
45+
template <typename T> struct Variadic;
46+
template <typename... Ts> struct Multi;
47+
48+
template <typename T> struct Ref;
49+
50+
extern int main(int, char**);
51+
52+
//////////////////////////////////////////////////////////////////////
53+
54+
namespace detail {
55+
56+
//////////////////////////////////////////////////////////////////////
57+
58+
// Scoped timing in debug builds. The emitted messages are supplied
59+
// with lambdas to avoid overhead when the code is compiled out.
60+
struct Timer {
61+
Timer() : m_msg{nullptr} {
62+
ONTRACE(2, [this] { m_begin = std::chrono::steady_clock::now(); }());
63+
}
64+
65+
explicit Timer(const char* msg) : m_msg{msg} {
66+
ONTRACE(2, [this] { m_begin = std::chrono::steady_clock::now(); }());
67+
}
68+
69+
template <typename F>
70+
explicit Timer(const F& f) {
71+
ONTRACE(2, [&] {
72+
m_str = f();
73+
m_msg = m_str.c_str();
74+
m_begin = std::chrono::steady_clock::now();
75+
}());
76+
}
77+
78+
// Stop the timer early before destruction with the given message.
79+
template<typename F>
80+
void stopWithMessage(const F& f) {
81+
ONTRACE(2, [&] {
82+
m_msg = nullptr;
83+
FTRACE(2, "{} (took {})\n", f(), elapsed());
84+
}());
85+
}
86+
87+
~Timer() {
88+
ONTRACE(2, [this] {
89+
if (!m_msg) return;
90+
FTRACE(2, "{} took {}\n", m_msg, elapsed());
91+
}());
92+
}
93+
94+
private:
95+
std::string elapsed() const {
96+
auto const d = std::chrono::duration_cast<std::chrono::duration<double>>(
97+
std::chrono::steady_clock::now() - m_begin
98+
).count();
99+
return folly::prettyPrint(d, folly::PRETTY_TIME, false);
100+
}
101+
102+
std::chrono::steady_clock::time_point m_begin;
103+
const char* m_msg;
104+
std::string m_str;
105+
106+
TRACE_SET_MOD(extern_worker);
107+
};
108+
109+
// Execute the given lambda, wrapping it with a Timer.
110+
template <typename F> auto time(const char* msg, const F& f) {
111+
Timer _{msg};
112+
return f();
113+
}
114+
115+
template <typename F1, typename F2> auto time(const F1& f1, const F2& f2) {
116+
Timer _{f1};
117+
return f2();
118+
}
119+
120+
//////////////////////////////////////////////////////////////////////
121+
122+
// Read/write to a file
123+
extern std::string readFile(const folly::fs::path&);
124+
extern void writeFile(const folly::fs::path&, const char*, size_t);
125+
126+
//////////////////////////////////////////////////////////////////////
127+
128+
// Matchers for the special "marker" classes
129+
130+
template <typename T>
131+
struct IsVariadic : std::false_type {};
132+
template <typename T>
133+
struct IsVariadic<Variadic<T>> : std::true_type {};
134+
135+
template <typename T>
136+
struct IsOpt : std::false_type {};
137+
template <typename T>
138+
struct IsOpt<Opt<T>> : std::true_type {};
139+
140+
template <typename... Ts>
141+
struct IsMulti : std::false_type {};
142+
template <typename... Ts>
143+
struct IsMulti<Multi<Ts...>> : std::true_type {};
144+
145+
template <typename T>
146+
using IsMarker = std::disjunction<IsVariadic<T>, IsOpt<T>, IsMulti<T>>;
147+
148+
//////////////////////////////////////////////////////////////////////
149+
150+
// Matchers for special non-marker classes
151+
152+
template <typename T>
153+
struct IsRef : std::false_type {};
154+
template <typename T>
155+
struct IsRef<Ref<T>> : std::true_type {};
156+
157+
template <typename T>
158+
struct IsVector : std::false_type {};
159+
template <typename T>
160+
struct IsVector<std::vector<T>> : std::true_type {};
161+
162+
template <typename T>
163+
struct IsOptional : std::false_type {};
164+
template <typename T>
165+
struct IsOptional<Optional<T>> : std::true_type {};
166+
167+
template <typename T>
168+
struct IsTuple : std::false_type {};
169+
template <typename... Ts>
170+
struct IsTuple<std::tuple<Ts...>> : std::true_type {};
171+
172+
//////////////////////////////////////////////////////////////////////
173+
174+
// Given a callable, Params<>::type is the callable's parameter types
175+
// as a tuple.
176+
template <typename> struct Params;
177+
template <typename R, typename... P>
178+
struct Params<R(P...)> {
179+
using type = std::tuple<
180+
typename std::remove_cv<typename std::remove_reference<P>::type>::type...
181+
>;
182+
};
183+
184+
// Given a callable, Return<>::type is the callable's return type.
185+
template <typename> struct Return;
186+
template <typename R, typename... P>
187+
struct Return<R(P...)> {
188+
using type =
189+
typename std::remove_cv<typename std::remove_reference<R>::type>::type;
190+
};
191+
192+
//////////////////////////////////////////////////////////////////////
193+
194+
// Given a type T, ToRef<T>::type is equivalent Ref type. For most
195+
// types this is just Ref<T>, but the variadic marker becomes a
196+
// std::vector of Refs, and an optional marker becomes an Optional
197+
// Ref.
198+
template <typename T> struct ToRef {
199+
using type = Ref<T>;
200+
};
201+
template <typename T> struct ToRef<Variadic<T>> {
202+
using type = std::vector<typename ToRef<T>::type>;
203+
};
204+
template <typename T> struct ToRef<Opt<T>> {
205+
using type = Optional<typename ToRef<T>::type>;
206+
};
207+
208+
//////////////////////////////////////////////////////////////////////
209+
210+
// Given a tuple of types, ToRefTuple<T>::type is a tuple with every
211+
// type converted to its ToRef equivalent.
212+
template <typename> struct ToRefTuple {};
213+
template <typename... Ts> struct ToRefTuple<std::tuple<Ts...>> {
214+
using type = std::tuple<typename ToRef<Ts>::type...>;
215+
};
216+
217+
// Same as ToRefTuple, except meant for return types. The only
218+
// difference is that a return type of the Multi marker becomes a
219+
// tuple.
220+
template <typename T> struct ToRefReturn {
221+
using type = typename ToRef<T>::type;
222+
};
223+
template <typename... Ts> struct ToRefReturn<Multi<Ts...>> {
224+
using type = std::tuple<typename ToRef<Ts>::type...>;
225+
};
226+
227+
//////////////////////////////////////////////////////////////////////
228+
229+
// Given a class (presumed to have static functions called init and
230+
// run), return their parameters or return values, transformed with
231+
// ToRef.
232+
template <typename C> struct ConfigRefs {
233+
using type =
234+
typename ToRefTuple<typename Params<decltype(C::init)>::type>::type;
235+
};
236+
237+
template <typename C> struct InputRefs {
238+
using type =
239+
typename ToRefTuple<typename Params<decltype(C::run)>::type>::type;
240+
};
241+
242+
template <typename C> struct ReturnRefs {
243+
using type =
244+
typename ToRefReturn<typename Return<decltype(C::run)>::type>::type;
245+
};
246+
247+
//////////////////////////////////////////////////////////////////////
248+
249+
// Iterate over a tuple. Just a wrapper around folly::for_each, which
250+
// doesn't work on empty tuples for some reason.
251+
template <typename F, typename... Ts>
252+
void for_each(std::tuple<Ts...>&& tuple, F&& f) {
253+
if constexpr (sizeof...(Ts) != 0) {
254+
folly::for_each(std::move(tuple), std::forward<F>(f));
255+
}
256+
}
257+
template <typename F, typename... Ts>
258+
void for_each(std::tuple<Ts...>& tuple, F&& f) {
259+
if constexpr (sizeof...(Ts) != 0) {
260+
folly::for_each(tuple, std::forward<F>(f));
261+
}
262+
}
263+
template <typename F, typename... Ts>
264+
void for_each(const std::tuple<Ts...>& tuple, F&& f) {
265+
if constexpr (sizeof...(Ts) != 0) {
266+
folly::for_each(tuple, std::forward<F>(f));
267+
}
268+
}
269+
270+
//////////////////////////////////////////////////////////////////////
271+
272+
// Empty class wrapping a Type. Meant to allow passing the type from
273+
// typesToValues as an argument.
274+
template <typename T> struct Tag { using Type = T; };
275+
276+
//////////////////////////////////////////////////////////////////////
277+
278+
// Instantiated on a tuple, and given a callable, call the callable
279+
// for each type in that tuple. The callable is called with the tuple
280+
// index as the first parameter, and Tag<T> as the second (where T is
281+
// the type at that tuple index).
282+
template <typename Tuple, typename F, size_t... Is>
283+
auto typesToValuesImpl(F&& f,
284+
std::index_sequence<Is...>) {
285+
return std::make_tuple(f(Is, Tag<std::tuple_element_t<Is, Tuple>>{})...);
286+
}
287+
288+
template <typename Tuple, typename F>
289+
auto typesToValues(F&& f) {
290+
return typesToValuesImpl<Tuple>(
291+
std::forward<F>(f),
292+
std::make_index_sequence<std::tuple_size<Tuple>{}>{}
293+
);
294+
}
295+
296+
//////////////////////////////////////////////////////////////////////
297+
298+
// Base class for Jobs. This provide a consistent interface to invoke
299+
// through.
300+
struct JobBase {
301+
const std::string& name() const { return m_name; }
302+
protected:
303+
explicit JobBase(const std::string& name);
304+
virtual ~JobBase() = default;
305+
306+
template <typename T> static T deserialize(const folly::fs::path&);
307+
template <typename T> static void serialize(const T&,
308+
size_t,
309+
const folly::fs::path&);
310+
311+
private:
312+
virtual void init(const folly::fs::path&) const = 0;
313+
virtual void fini() const = 0;
314+
virtual void run(const folly::fs::path&, const folly::fs::path&) const = 0;
315+
316+
std::string m_name;
317+
318+
friend int HPHP::extern_worker::main(int, char**);
319+
};
320+
321+
//////////////////////////////////////////////////////////////////////
322+
323+
}}

0 commit comments

Comments
 (0)