diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 96dc898b6d..f100936676 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -946,7 +946,7 @@ jobs: # Windows - name: Windows init steps (vc142) run: | - vcpkg.exe install boost-algorithm boost-filesystem boost-multi-index boost-multiprecision boost-program-options boost-system boost-unordered boost-uuid + vcpkg.exe install boost-thread boost-algorithm boost-filesystem boost-multi-index boost-multiprecision boost-program-options boost-system boost-unordered boost-uuid vcpkg.exe integrate install env: PYTHON_VERSION: ${{ matrix.python-version }} @@ -956,7 +956,7 @@ jobs: - name: Windows init steps (vc143) run: | - vcpkg.exe install boost-algorithm boost-filesystem boost-multi-index boost-multiprecision boost-program-options boost-system boost-unordered boost-uuid + vcpkg.exe install boost-thread boost-algorithm boost-filesystem boost-multi-index boost-multiprecision boost-program-options boost-system boost-unordered boost-uuid vcpkg.exe integrate install env: PYTHON_VERSION: ${{ matrix.python-version }} @@ -1313,6 +1313,11 @@ jobs: - is-full-run: false os: macos-11 + # GitHub Actions are broken on OSX, don't test 3.7 wheels + # https://github.com/actions/setup-python/issues/682 + - python-version: 3.7 + os: macos-11 + # Exclude Python 3.7, 3.8, 3.10, 3.11 builds - is-full-run: false python-version: 3.7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c5dcf4283..9584a28ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ +# [v2.3.0](https://github.com/finos/perspective/releases/tag/v2.3.0) + +_20 June 2023_ ([Full changelog](https://github.com/finos/perspective/compare/v2.2.1...v2.3.0)) + +**Breaking** + +- Thread-safe python readers [#2261](https://github.com/finos/perspective/pull/2261) + +Fixes + +- Fix workspace `save()` w/settings [#2257](https://github.com/finos/perspective/pull/2257) + +Misc + +- Validate installed emsdk version [#2256](https://github.com/finos/perspective/pull/2256) +- Allow `pandas~=2.0.0` to be installed [#2244](https://github.com/finos/perspective/pull/2244) + # [v2.2.1](https://github.com/finos/perspective/releases/tag/v2.2.1) -_5 June 2023_ ([Full changelog](https://github.com/finos/perspective/compare/v2.2.0...v2.2.1)) +_4 June 2023_ ([Full changelog](https://github.com/finos/perspective/compare/v2.2.0...v2.2.1)) Features diff --git a/cpp/perspective/CMakeLists.txt b/cpp/perspective/CMakeLists.txt index c14e9d5d78..6a7af9e6c4 100644 --- a/cpp/perspective/CMakeLists.txt +++ b/cpp/perspective/CMakeLists.txt @@ -279,9 +279,10 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD) set(OPT_FLAGS " \ -O3 \ -g1 \ + -flto \ ") if (PSP_PYODIDE) - set(OPT_FLAGS "${OPT_FLAGS} -flto") + set(OPT_FLAGS "${OPT_FLAGS} -flto") endif () endif() endif() @@ -291,8 +292,11 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD) # Boost is a system dependency and must be present and built on the system. if(PSP_PYODIDE) set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH};/usr/local/") + find_package(Boost REQUIRED) + else() + set(Boost_USE_STATIC_LIBS ON) + find_package(Boost REQUIRED COMPONENTS system thread) endif() - find_package(Boost REQUIRED) if(NOT Boost_FOUND) message(FATAL_ERROR "${Red}Boost could not be located${ColorReset}") @@ -607,8 +611,8 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD) include_directories(${PSP_PYTHON_SRC}/include) - target_compile_definitions(psp PRIVATE PSP_ENABLE_PYTHON=1) - target_compile_definitions(psppy PRIVATE PSP_ENABLE_PYTHON=1) + target_compile_definitions(psp PRIVATE PSP_ENABLE_PYTHON=1 PSP_PARALLEL_FOR=1) + target_compile_definitions(psppy PRIVATE PSP_ENABLE_PYTHON=1 PSP_PARALLEL_FOR=1) if(WIN32) target_compile_definitions(psppy PRIVATE WIN32=1) @@ -628,7 +632,7 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD) endif() # Link against minimal arrow static library - target_link_libraries(psp arrow re2) + target_link_libraries(psp arrow re2 Boost::thread) target_link_libraries(psppy psp) # The compiled libraries will be put in CMAKE_LIBRARY_OUTPUT_DIRECTORY by default. In the diff --git a/cpp/perspective/package.json b/cpp/perspective/package.json index d1aad9691d..d084a2e1f2 100644 --- a/cpp/perspective/package.json +++ b/cpp/perspective/package.json @@ -3,7 +3,7 @@ "private": true, "author": "The Perspective Authors", "license": "Apache-2.0", - "version": "2.2.1", + "version": "2.3.0", "main": "./dist/esm/perspective.cpp.js", "files": [ "dist/esm/**/*", diff --git a/cpp/perspective/src/cpp/arrow_loader.cpp b/cpp/perspective/src/cpp/arrow_loader.cpp index f0365f436f..1604e17be2 100644 --- a/cpp/perspective/src/cpp/arrow_loader.cpp +++ b/cpp/perspective/src/cpp/arrow_loader.cpp @@ -161,31 +161,31 @@ namespace apachearrow { std::shared_ptr schema = m_table->schema(); std::vector> fields = schema->fields(); - for (long unsigned int cidx = 0; cidx < m_names.size(); ++cidx) { + parallel_for(int(m_names.size()), [&](int cidx) { auto name = m_names[cidx]; t_dtype type = m_types[cidx]; - if (!input_schema.has_column(name)) { + if (input_schema.has_column(name)) { // Skip columns that are defined in the arrow but not // in the Table's input schema. - continue; - } - auto raw_type = fields[cidx]->type()->name(); + auto raw_type = fields[cidx]->type()->name(); - if (name == "__INDEX__") { - implicit_index = true; - std::shared_ptr pkey_col_sptr - = tbl.add_column_sptr("psp_pkey", type, true); - fill_column(tbl, pkey_col_sptr, "psp_pkey", cidx, type, - raw_type, is_update); - tbl.clone_column("psp_pkey", "psp_okey"); - continue; - } else { - auto col = tbl.get_column(name); - fill_column(tbl, col, name, cidx, type, raw_type, is_update); + if (name == "__INDEX__") { + implicit_index = true; + std::shared_ptr pkey_col_sptr + = tbl.add_column_sptr("psp_pkey", type, true); + fill_column(tbl, pkey_col_sptr, "psp_pkey", cidx, type, + raw_type, is_update); + tbl.clone_column("psp_pkey", "psp_okey"); + // continue; + } else { + auto col = tbl.get_column(name); + fill_column( + tbl, col, name, cidx, type, raw_type, is_update); + } } - } + }); // Fill index column - recreated every time a `t_data_table` is created. if (!implicit_index) { diff --git a/cpp/perspective/src/cpp/arrow_writer.cpp b/cpp/perspective/src/cpp/arrow_writer.cpp index 582190c759..947ede89e9 100644 --- a/cpp/perspective/src/cpp/arrow_writer.cpp +++ b/cpp/perspective/src/cpp/arrow_writer.cpp @@ -69,11 +69,6 @@ namespace apachearrow { get_scalar(t_tscalar& t) { return t.get(); } - template <> - std::string - get_scalar(t_tscalar& t) { - return t.to_string(); - } // std::int32_t // get_idx(std::int32_t cidx, std::int32_t ridx, std::int32_t stride, diff --git a/cpp/perspective/src/cpp/emscripten.cpp b/cpp/perspective/src/cpp/emscripten.cpp index 2da4804afd..2fca21d056 100644 --- a/cpp/perspective/src/cpp/emscripten.cpp +++ b/cpp/perspective/src/cpp/emscripten.cpp @@ -161,7 +161,7 @@ namespace binding { default: { std::wstring_convert converter( "", L""); - return t_val(converter.from_bytes(scalar.to_string())); + return t_val(converter.from_bytes(scalar.get())); } } } @@ -459,7 +459,7 @@ namespace binding { for (int idx = 0; idx < data_size; idx++) { t_tscalar scalar = data[idx]; if (scalar.is_valid() && scalar.get_dtype() != DTYPE_NONE) { - auto adx = vocab.get_interned(scalar.to_string()); + auto adx = vocab.get_interned(scalar.get()); indexArray.call("fill", t_val(adx), idx, idx + 1); validityMap[idx / 32] |= 1 << (idx % 32); } else { diff --git a/cpp/perspective/src/cpp/gnode.cpp b/cpp/perspective/src/cpp/gnode.cpp index c05e1814fe..7d9a67b6c0 100644 --- a/cpp/perspective/src/cpp/gnode.cpp +++ b/cpp/perspective/src/cpp/gnode.cpp @@ -19,13 +19,9 @@ #include #include #include - #include #include - -#ifdef PSP_ENABLE_PYTHON #include -#endif namespace perspective { @@ -48,6 +44,9 @@ calc_negate(t_tscalar val) { t_gnode::t_gnode(const t_schema& input_schema, const t_schema& output_schema) : m_mode(NODE_PROCESSING_SIMPLE_DATAFLOW) +#ifdef PSP_PARALLEL_FOR + , m_lock(nullptr) +#endif , m_gnode_type(GNODE_TYPE_PKEYED) , m_input_schema(input_schema) , m_output_schema(output_schema) @@ -586,10 +585,8 @@ bool t_gnode::process(t_uindex port_id) { PSP_TRACE_SENTINEL(); PSP_VERBOSE_ASSERT(m_init, "Cannot `process` on an uninited gnode."); -#ifdef PSP_ENABLE_PYTHON - PerspectiveScopedGILRelease acquire(m_event_loop_thread_id); -#endif - + PSP_GIL_UNLOCK(); + PSP_WRITE_LOCK(m_lock); t_process_table_result result = _process_table(port_id); if (result.m_flattened_data_table) { @@ -1324,6 +1321,8 @@ t_gnode::clear_input_ports() { void t_gnode::clear_output_ports() { + PSP_GIL_UNLOCK(); + PSP_WRITE_LOCK(m_lock); for (t_uindex idx = 0, loop_end = m_oports.size(); idx < loop_end; ++idx) { m_oports[idx]->get_table()->clear(); } @@ -1356,10 +1355,10 @@ t_gnode::repr() const { return ss.str(); } -#ifdef PSP_ENABLE_PYTHON +#ifdef PSP_PARALLEL_FOR void -t_gnode::set_event_loop_thread_id(std::thread::id id) { - m_event_loop_thread_id = id; +t_gnode::set_lock(boost::shared_mutex* lock) { + m_lock = lock; } #endif diff --git a/cpp/perspective/src/cpp/pool.cpp b/cpp/perspective/src/cpp/pool.cpp index b662211e0d..0b4d959c8c 100644 --- a/cpp/perspective/src/cpp/pool.cpp +++ b/cpp/perspective/src/cpp/pool.cpp @@ -48,7 +48,9 @@ empty_callback() { t_pool::t_pool() : m_update_delegate(empty_callback()) - , m_event_loop_thread_id(std::thread::id()) +#ifdef PSP_PARALLEL_FOR + , m_lock(new boost::shared_mutex()) +#endif , m_sleep(0) { m_run.clear(); } @@ -62,7 +64,11 @@ t_pool::t_pool() #endif -t_pool::~t_pool() {} +t_pool::~t_pool() { +#ifdef PSP_PARALLEL_FOR + delete m_lock; +#endif +} void t_pool::init() { @@ -84,10 +90,8 @@ t_pool::register_gnode(t_gnode* node) { t_uindex id = m_gnodes.size() - 1; node->set_id(id); node->set_pool_cleanup([this, id]() { this->m_gnodes[id] = 0; }); -#ifdef PSP_ENABLE_PYTHON - if (m_event_loop_thread_id != std::thread::id()) { - node->set_event_loop_thread_id(m_event_loop_thread_id); - } +#ifdef PSP_PARALLEL_FOR + node->set_lock(m_lock); #endif if (t_env::log_progress()) { @@ -132,18 +136,10 @@ t_pool::send(t_uindex gnode_id, t_uindex port_id, const t_data_table& table) { } } -#ifdef PSP_ENABLE_PYTHON -void -t_pool::set_event_loop() { - m_event_loop_thread_id = std::this_thread::get_id(); - for (auto node : m_gnodes) { - node->set_event_loop_thread_id(m_event_loop_thread_id); - } -} - -std::thread::id -t_pool::get_event_loop_thread_id() const { - return m_event_loop_thread_id; +#ifdef PSP_PARALLEL_FOR +boost::shared_mutex* +t_pool::get_lock() const { + return m_lock; } #endif diff --git a/cpp/perspective/src/cpp/pyutils.cpp b/cpp/perspective/src/cpp/pyutils.cpp index d1634e49d3..52cae853af 100644 --- a/cpp/perspective/src/cpp/pyutils.cpp +++ b/cpp/perspective/src/cpp/pyutils.cpp @@ -10,30 +10,16 @@ #include #include #include -#ifdef PSP_ENABLE_PYTHON + namespace perspective { -PerspectiveScopedGILRelease::PerspectiveScopedGILRelease( - std::thread::id event_loop_thread_id) - : m_thread_state(NULL) { - if (event_loop_thread_id != std::thread::id()) { - if (std::this_thread::get_id() != event_loop_thread_id) { - std::stringstream err; - err << "Perspective called from wrong thread; Expected " - << event_loop_thread_id << "; Got " - << std::this_thread::get_id() << std::endl; - PSP_COMPLAIN_AND_ABORT(err.str()); - } - m_thread_state = PyEval_SaveThread(); - } -} +#ifdef PSP_ENABLE_PYTHON +PerspectiveGILUnlock::PerspectiveGILUnlock() + : m_thread_state(PyEval_SaveThread()) {} -PerspectiveScopedGILRelease::~PerspectiveScopedGILRelease() { - if (m_thread_state != NULL) { - PyEval_RestoreThread(m_thread_state); - } +PerspectiveGILUnlock::~PerspectiveGILUnlock() { + PyEval_RestoreThread(m_thread_state); } +#endif } // end namespace perspective - -#endif \ No newline at end of file diff --git a/cpp/perspective/src/cpp/view.cpp b/cpp/perspective/src/cpp/view.cpp index e0221f7e05..bc70bfe6da 100644 --- a/cpp/perspective/src/cpp/view.cpp +++ b/cpp/perspective/src/cpp/view.cpp @@ -13,6 +13,7 @@ #include #include +#include namespace perspective { @@ -76,6 +77,8 @@ template View::~View() { auto pool = m_table->get_pool(); auto gnode = m_table->get_gnode(); + PSP_GIL_UNLOCK(); + PSP_WRITE_LOCK(pool->get_lock()); // TODO: need to invalidate memory used by previous computed columns // without affecting views that depend on those computed columns. pool->unregister_context(gnode->get_id(), m_name); @@ -196,7 +199,7 @@ View::column_names(bool skip, std::int32_t depth) const { for (t_uindex key = 0, max = m_ctx->unity_get_column_count(); key != max; ++key) { t_tscalar name = m_ctx->get_column_name(key); - if (name.to_string() == "psp_okey") { + if (strcmp(name.get(), "psp_okey") == 0) { continue; }; std::vector col_path; @@ -215,7 +218,7 @@ View::column_names(bool skip, std::int32_t depth) const { for (t_uindex key = 0, max = m_ctx->unity_get_column_count(); key != max; ++key) { t_tscalar name = m_ctx->get_column_name(key); - if (name.to_string() == "psp_okey") { + if (strcmp(name.get(), "psp_okey") == 0) { continue; }; std::vector col_path; @@ -594,17 +597,18 @@ View::data_slice_to_batches( std::vector> vectors; std::vector> fields; - std::int32_t num_columns = end_col - start_col; - std::vector row_pivots = m_view_config->get_row_pivots(); t_uindex num_row_paths = emit_group_by ? row_pivots.size() : 0; if (num_columns + num_row_paths > 0) { - fields.reserve(num_columns + num_row_paths); - vectors.reserve(num_columns + num_row_paths); + fields.resize(num_columns + num_row_paths); + vectors.resize(num_columns + num_row_paths); } - if (emit_group_by && num_row_paths > 0 && !is_column_only()) { + auto num_output_row_paths = is_column_only() ? 0 : num_row_paths; + + t_uindex write_idx = 0; + if (emit_group_by && num_output_row_paths > 0) { auto schema = m_table->get_schema(); for (auto rpidx = 0; rpidx < num_row_paths; ++rpidx) { std::string column_name = row_pivots.at(rpidx); @@ -631,149 +635,159 @@ View::data_slice_to_batches( std::shared_ptr arr; switch (dtype) { case DTYPE_INT8: { - fields.push_back( - arrow::field(row_path_name, arrow::int8())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::int8()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_UINT8: { - fields.push_back( - arrow::field(row_path_name, arrow::uint8())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::uint8()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_INT16: { - fields.push_back( - arrow::field(row_path_name, arrow::int16())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::int16()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_UINT16: { - fields.push_back( - arrow::field(row_path_name, arrow::uint16())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::uint16()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_INT32: { - fields.push_back( - arrow::field(row_path_name, arrow::int32())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::int32()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_UINT32: { - fields.push_back( - arrow::field(row_path_name, arrow::uint32())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::uint32()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_INT64: { - fields.push_back( - arrow::field(row_path_name, arrow::int64())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::int64()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_UINT64: { - fields.push_back( - arrow::field(row_path_name, arrow::uint64())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::uint64()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_FLOAT32: { - fields.push_back( - arrow::field(row_path_name, arrow::float32())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::float32()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_FLOAT64: { - fields.push_back( - arrow::field(row_path_name, arrow::float64())); - arr = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] + = arrow::field(row_path_name, arrow::float64()); + vectors[write_idx] + = apachearrow::numeric_col_to_array(extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_DATE: { - fields.push_back( - arrow::field(row_path_name, arrow::date32())); - arr = apachearrow::date_col_to_array( + fields[write_idx] + = arrow::field(row_path_name, arrow::date32()); + vectors[write_idx] = apachearrow::date_col_to_array( extents, [&, rpidx](t_uindex ridx) { auto depth = m_ctx->unity_get_row_depth(ridx); if (rpidx < depth) { @@ -785,9 +799,9 @@ View::data_slice_to_batches( }); } break; case DTYPE_TIME: { - fields.push_back(arrow::field(row_path_name, - arrow::timestamp(arrow::TimeUnit::MILLI))); - arr = apachearrow::timestamp_col_to_array( + fields[write_idx] = arrow::field(row_path_name, + arrow::timestamp(arrow::TimeUnit::MILLI)); + vectors[write_idx] = apachearrow::timestamp_col_to_array( extents, [&, rpidx](t_uindex ridx) { auto depth = m_ctx->unity_get_row_depth(ridx); if (rpidx < depth) { @@ -799,9 +813,9 @@ View::data_slice_to_batches( }); } break; case DTYPE_BOOL: { - fields.push_back( - arrow::field(row_path_name, arrow::boolean())); - arr = apachearrow::boolean_col_to_array( + fields[write_idx] + = arrow::field(row_path_name, arrow::boolean()); + vectors[write_idx] = apachearrow::boolean_col_to_array( extents, [&, rpidx](t_uindex ridx) { auto depth = m_ctx->unity_get_row_depth(ridx); if (rpidx < depth) { @@ -813,18 +827,19 @@ View::data_slice_to_batches( }); } break; case DTYPE_STR: { - fields.push_back(arrow::field(row_path_name, - arrow::dictionary(arrow::int32(), arrow::utf8()))); - arr = apachearrow::string_col_to_dictionary_array( - extents, [&, rpidx](t_uindex ridx) { - auto depth = m_ctx->unity_get_row_depth(ridx); - if (rpidx < depth) { - return m_ctx->unity_get_row_path(ridx).at( - (depth - 1) - rpidx); - } else { - return mknone(); - } - }); + fields[write_idx] = arrow::field(row_path_name, + arrow::dictionary(arrow::int32(), arrow::utf8())); + vectors[write_idx] + = apachearrow::string_col_to_dictionary_array( + extents, [&, rpidx](t_uindex ridx) { + auto depth = m_ctx->unity_get_row_depth(ridx); + if (rpidx < depth) { + return m_ctx->unity_get_row_path(ridx).at( + (depth - 1) - rpidx); + } else { + return mknone(); + } + }); } break; case DTYPE_OBJECT: default: { @@ -835,7 +850,8 @@ View::data_slice_to_batches( PSP_COMPLAIN_AND_ABORT(ss.str()); } } - vectors.push_back(arr); + + write_idx++; } } @@ -843,10 +859,10 @@ View::data_slice_to_batches( // the number of hidden sorts, so we can skip hidden sorts. // t_uindex num_view_columns = num_columns - m_hidden_sort.size(); t_uindex num_view_columns = m_columns.size(); - - for (auto cidx = start_col; cidx < end_col; ++cidx) { + std::vector indices; + for (auto tidx = 0; tidx < end_col - start_col; ++tidx) { + auto cidx = tidx + start_col; if (cidx == start_col && num_sides > 0) { - // TODO: write row_paths continue; } @@ -859,6 +875,16 @@ View::data_slice_to_batches( continue; } + indices.push_back(tidx); + } + + // TODO For some reason, this parallel call doesn't benefit from + // parallelism. + parallel_for(int(indices.size()), [&](auto iidx) { + // for (auto iidx = 0; iidx < indices.size(); iidx++) { + auto ccidx = iidx + num_output_row_paths; + auto cidx = indices[iidx] + start_col; + std::vector col_path = names.at(cidx); t_dtype dtype = get_column_dtype(cidx); @@ -882,125 +908,125 @@ View::data_slice_to_batches( std::shared_ptr arr; switch (dtype) { case DTYPE_INT8: { - fields.push_back(arrow::field(name, arrow::int8())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::int8()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_UINT8: { - fields.push_back(arrow::field(name, arrow::uint8())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::uint8()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_INT16: { - fields.push_back(arrow::field(name, arrow::int16())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::int16()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_UINT16: { - fields.push_back(arrow::field(name, arrow::uint16())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::uint16()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_INT32: { - fields.push_back(arrow::field(name, arrow::int32())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::int32()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_UINT32: { - fields.push_back(arrow::field(name, arrow::uint32())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::uint32()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_INT64: { - fields.push_back(arrow::field(name, arrow::int64())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::int64()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_UINT64: { - fields.push_back(arrow::field(name, arrow::uint64())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::uint64()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_FLOAT32: { - fields.push_back(arrow::field(name, arrow::float32())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::float32()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_FLOAT64: { - fields.push_back(arrow::field(name, arrow::float64())); - arr = apachearrow::numeric_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { - return slice[(ridx - extents.m_srow) * stride - + (cidx - extents.m_scol)]; - }); + fields[ccidx] = arrow::field(name, arrow::float64()); + vectors[ccidx] + = apachearrow::numeric_col_to_array(extents, [&](t_uindex ridx) { + return slice[(ridx - extents.m_srow) * stride + + (cidx - extents.m_scol)]; + }); } break; case DTYPE_DATE: { - fields.push_back(arrow::field(name, arrow::date32())); - arr = apachearrow::date_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { + fields[ccidx] = arrow::field(name, arrow::date32()); + vectors[ccidx] = apachearrow::date_col_to_array( + extents, [&](t_uindex ridx) { return slice[(ridx - extents.m_srow) * stride + (cidx - extents.m_scol)]; }); } break; case DTYPE_TIME: { - fields.push_back(arrow::field( - name, arrow::timestamp(arrow::TimeUnit::MILLI))); - arr = apachearrow::timestamp_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { + fields[ccidx] = arrow::field( + name, arrow::timestamp(arrow::TimeUnit::MILLI)); + vectors[ccidx] = apachearrow::timestamp_col_to_array( + extents, [&](t_uindex ridx) { return slice[(ridx - extents.m_srow) * stride + (cidx - extents.m_scol)]; }); } break; case DTYPE_BOOL: { - fields.push_back(arrow::field(name, arrow::boolean())); - arr = apachearrow::boolean_col_to_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { + fields[ccidx] = arrow::field(name, arrow::boolean()); + vectors[ccidx] = apachearrow::boolean_col_to_array( + extents, [&](t_uindex ridx) { return slice[(ridx - extents.m_srow) * stride + (cidx - extents.m_scol)]; }); } break; case DTYPE_STR: { - fields.push_back(arrow::field( - name, arrow::dictionary(arrow::int32(), arrow::utf8()))); - arr = apachearrow::string_col_to_dictionary_array( - extents, [slice, cidx, stride, extents](t_uindex ridx) { + fields[ccidx] = arrow::field( + name, arrow::dictionary(arrow::int32(), arrow::utf8())); + vectors[ccidx] = apachearrow::string_col_to_dictionary_array( + extents, [&](t_uindex ridx) { return slice[(ridx - extents.m_srow) * stride + (cidx - extents.m_scol)]; }); @@ -1014,8 +1040,11 @@ View::data_slice_to_batches( PSP_COMPLAIN_AND_ABORT(ss.str()); } } - vectors.push_back(arr); - } + }); + // } + + fields.resize(indices.size() + num_output_row_paths); + vectors.resize(indices.size() + num_output_row_paths); auto arrow_schema = arrow::schema(fields); auto num_rows = data_slice->num_rows(); @@ -1314,12 +1343,12 @@ View::is_column_only() const { return m_view_config->is_column_only(); } -#ifdef PSP_ENABLE_PYTHON +#ifdef PSP_PARALLEL_FOR template -std::thread::id -View::get_event_loop_thread_id() const { - return m_table->get_pool()->get_event_loop_thread_id(); -}; +boost::shared_mutex* +View::get_lock() const { + return m_table->get_pool()->get_lock(); +} #endif /****************************************************************************** diff --git a/cpp/perspective/src/include/perspective/arrow_writer.h b/cpp/perspective/src/include/perspective/arrow_writer.h index 44256b90fd..588672e9d7 100644 --- a/cpp/perspective/src/include/perspective/arrow_writer.h +++ b/cpp/perspective/src/include/perspective/arrow_writer.h @@ -122,7 +122,7 @@ namespace apachearrow { // auto idx = get_idx(cidx, ridx, stride, extents); t_tscalar scalar = f(ridx); if (scalar.is_valid() && scalar.get_dtype() != DTYPE_NONE) { - auto adx = vocab.get_interned(scalar.to_string()); + auto adx = vocab.get_interned(scalar.get()); indices_builder.UnsafeAppend(adx); } else { indices_builder.UnsafeAppendNull(); diff --git a/cpp/perspective/src/include/perspective/first.h b/cpp/perspective/src/include/perspective/first.h index 0c987df866..efd86c5627 100644 --- a/cpp/perspective/src/include/perspective/first.h +++ b/cpp/perspective/src/include/perspective/first.h @@ -7,14 +7,6 @@ * */ -#ifndef PSP_ENABLE_WASM -#ifdef PSP_ENABLE_PYTHON_THREADING -#ifndef PSP_PARALLEL_FOR -#define PSP_PARALLEL_FOR -#endif -#endif -#endif - #if !defined(__linux__) && !defined(__APPLE__) && !defined(WIN32) // default to linux #define __linux__ diff --git a/cpp/perspective/src/include/perspective/gnode.h b/cpp/perspective/src/include/perspective/gnode.h index e67eee8903..380258d105 100644 --- a/cpp/perspective/src/include/perspective/gnode.h +++ b/cpp/perspective/src/include/perspective/gnode.h @@ -26,10 +26,12 @@ #include #include #include +#include + #ifdef PSP_PARALLEL_FOR #include +#include #endif -#include namespace perspective { @@ -208,8 +210,8 @@ class PERSPECTIVE_EXPORT t_gnode { std::shared_ptr get_expression_vocab() const; std::shared_ptr get_expression_regex_mapping() const; -#ifdef PSP_ENABLE_PYTHON - void set_event_loop_thread_id(std::thread::id id); +#ifdef PSP_PARALLEL_FOR + void set_lock(boost::shared_mutex* lock); #endif protected: @@ -364,8 +366,8 @@ class PERSPECTIVE_EXPORT t_gnode { std::shared_ptr m_expression_vocab; std::shared_ptr m_expression_regex_mapping; -#ifdef PSP_ENABLE_PYTHON - std::thread::id m_event_loop_thread_id; +#ifdef PSP_PARALLEL_FOR + boost::shared_mutex* m_lock; #endif }; diff --git a/cpp/perspective/src/include/perspective/pool.h b/cpp/perspective/src/include/perspective/pool.h index 1c9f86db91..5ff8637dda 100644 --- a/cpp/perspective/src/include/perspective/pool.h +++ b/cpp/perspective/src/include/perspective/pool.h @@ -15,8 +15,9 @@ #include #include -#ifdef PSP_ENABLE_PYTHON +#ifdef PSP_PARALLEL_FOR #include +#include #endif #if defined PSP_ENABLE_WASM and !defined(PSP_ENABLE_PYTHON) @@ -61,9 +62,8 @@ class PERSPECTIVE_EXPORT t_pool { t_ctx_type type, std::int64_t ptr); #endif -#ifdef PSP_ENABLE_PYTHON - void set_event_loop(); - std::thread::id get_event_loop_thread_id() const; +#ifdef PSP_PARALLEL_FOR + boost::shared_mutex* get_lock() const; #endif /** @@ -110,8 +110,8 @@ class PERSPECTIVE_EXPORT t_pool { bool validate_gnode_id(t_uindex gnode_id) const; private: -#ifdef PSP_ENABLE_PYTHON - std::thread::id m_event_loop_thread_id; +#ifdef PSP_PARALLEL_FOR + boost::shared_mutex* m_lock; #endif std::mutex m_mtx; std::vector m_gnodes; diff --git a/cpp/perspective/src/include/perspective/pyutils.h b/cpp/perspective/src/include/perspective/pyutils.h index 854a21db0c..bb9aee25af 100644 --- a/cpp/perspective/src/include/perspective/pyutils.h +++ b/cpp/perspective/src/include/perspective/pyutils.h @@ -10,19 +10,41 @@ #pragma once #include -#ifdef PSP_ENABLE_PYTHON +#ifdef PSP_PARALLEL_FOR #include +#include +#include +#endif namespace perspective { -class PERSPECTIVE_EXPORT PerspectiveScopedGILRelease { +#ifdef PSP_PARALLEL_FOR +#define PSP_GIL_READ_LOCK(X) \ + auto _thread_state = PyEval_SaveThread(); \ + boost::shared_lock _lock(*X); \ + PyEval_RestoreThread(_thread_state); + +#define PSP_READ_LOCK(X) boost::shared_lock _lock(*X); +#define PSP_WRITE_LOCK(X) boost::unique_lock _lock(*X); +#else +#define PSP_GIL_READ_LOCK(X) +#define PSP_READ_LOCK(X) +#define PSP_WRITE_LOCK(X) +#endif + +#ifdef PSP_ENABLE_PYTHON +class PERSPECTIVE_EXPORT PerspectiveGILUnlock { public: - PerspectiveScopedGILRelease(std::thread::id event_loop_thread_id); - ~PerspectiveScopedGILRelease(); + PerspectiveGILUnlock(); + ~PerspectiveGILUnlock(); private: PyThreadState* m_thread_state; }; +#define PSP_GIL_UNLOCK() PerspectiveGILUnlock _acquire; +#else +#define PSP_GIL_UNLOCK() +#endif + } // namespace perspective -#endif \ No newline at end of file diff --git a/cpp/perspective/src/include/perspective/view.h b/cpp/perspective/src/include/perspective/view.h index 8ba822a08d..647d6b90be 100644 --- a/cpp/perspective/src/include/perspective/view.h +++ b/cpp/perspective/src/include/perspective/view.h @@ -258,8 +258,8 @@ class PERSPECTIVE_EXPORT View { t_stepdelta get_step_delta(t_index bidx, t_index eidx) const; t_dtype get_column_dtype(t_uindex idx) const; bool is_column_only() const; -#ifdef PSP_ENABLE_PYTHON - std::thread::id get_event_loop_thread_id() const; +#ifdef PSP_PARALLEL_FOR + boost::shared_mutex* get_lock() const; #endif private: diff --git a/docs/docs/python.md b/docs/docs/python.md index 6ea8de2040..aca9775a6b 100644 --- a/docs/docs/python.md +++ b/docs/docs/python.md @@ -266,10 +266,8 @@ using a websocket API. By default, `perspective` will run with a synchronous interface. Using the `PerspectiveManager.set_loop_callback()` method, `perspective` can be configured to defer the application of side-effectful calls like `update()` to an event -loop, such as `tornado.ioloop.IOLoop`. When running in Async mode, Perspective -will release the GIL for some operations, enabling better parallelism and -overall better server performance. There are a few important differences when -running `PerspectiveManager` in this mode: +loop, such as `tornado.ioloop.IOLoop`. There are a few important differences +when running `PerspectiveManager` in this mode: - Calls to methods like `update()` will return immediately, and the reciprocal `on_update()` callbacks will be invoked on an event later scheduled. Calls @@ -278,11 +276,9 @@ running `PerspectiveManager` in this mode: - Updates will be _conflated_ when multiple calls to `update()` occur before the scheduled application. In such cases, you may receive a single `on_update()` notification for multiple `update()` calls. -- Once `set_loop_callback()` has been called, you may not call Perspective - functions from any other thread. -For example, using Tornado `IOLoop` you can create a dedicated thread for a -`PerspectiveManager`: +For example, using Tornado `IOLoop` you can create a dedicated thread or pool +for a `PerspectiveManager`: ```python manager = perspective.PerspectiveManager() @@ -297,6 +293,23 @@ thread.daemon = True thread.start() ``` +#### Multi-threading + +When running in Async mode, Perspective will release the GIL while dispatching +to an internal thread pool for some operations, enabling better parallelism and +overall better server performance. However, Perspective's Python interface +itself will still process queries in a single queue. To enable parallel +query processing, call `set_loop_callback` with a multi-threaded executor +such as `concurrent.futures.ThreadPoolExecutor`: + +```python +def perspective_thread(): + loop = tornado.ioloop.IOLoop() + with concurrent.futures.ThreadPoolExecutor() as executor: + manager.set_loop_callback(loop.run_in_executor, executor) + loop.start() +``` + ### Hosting `Table` and `View` instances `PerspectiveManager` has the ability to "host" `perspective.Table` and diff --git a/docs/package.json b/docs/package.json index 35b553c1da..199449079c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@finos/perspective-docs", - "version": "2.2.1", + "version": "2.3.0", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -16,13 +16,13 @@ "dependencies": { "@docusaurus/core": "2.2.0", "@docusaurus/preset-classic": "2.2.0", - "@finos/perspective": "^2.2.1", - "@finos/perspective-viewer": "^2.2.1", - "@finos/perspective-viewer-d3fc": "^2.2.1", - "@finos/perspective-viewer-datagrid": "^2.2.1", - "@finos/perspective-webpack-plugin": "^2.2.1", + "@finos/perspective": "^2.3.0", + "@finos/perspective-viewer": "^2.3.0", + "@finos/perspective-viewer-d3fc": "^2.3.0", + "@finos/perspective-viewer-datagrid": "^2.3.0", + "@finos/perspective-webpack-plugin": "^2.3.0", "@mdx-js/react": "^1.6.22", - "blocks": "^2.2.1", + "blocks": "^2.3.0", "clsx": "^1.1.1", "prism-react-renderer": "^1.3.3", "react": "^17.0.2", diff --git a/examples/blocks/package.json b/examples/blocks/package.json index 9f59deaf36..85b7b743e3 100644 --- a/examples/blocks/package.json +++ b/examples/blocks/package.json @@ -1,7 +1,7 @@ { "name": "blocks", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "A collection of simple client-side Perspective examples for `http://bl.ocks.org`.", "scripts": { "start": "mkdirp dist && node server.js" @@ -10,12 +10,12 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.2.1", - "@finos/perspective-viewer": "^2.2.1", - "@finos/perspective-viewer-d3fc": "^2.2.1", - "@finos/perspective-viewer-datagrid": "^2.2.1", - "@finos/perspective-viewer-openlayers": "^2.2.1", - "@finos/perspective-workspace": "^2.2.1", + "@finos/perspective": "^2.3.0", + "@finos/perspective-viewer": "^2.3.0", + "@finos/perspective-viewer-d3fc": "^2.3.0", + "@finos/perspective-viewer-datagrid": "^2.3.0", + "@finos/perspective-viewer-openlayers": "^2.3.0", + "@finos/perspective-workspace": "^2.3.0", "superstore-arrow": "1.0.0" } } diff --git a/examples/esbuild-example/package.json b/examples/esbuild-example/package.json index 5974973592..f98dc087e8 100644 --- a/examples/esbuild-example/package.json +++ b/examples/esbuild-example/package.json @@ -1,7 +1,7 @@ { "name": "esbuild-example", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "An esbuild example app built using `@finos/perspective-viewer`.", "scripts": { "build": "node build.js", @@ -10,15 +10,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.2.1", - "@finos/perspective-viewer": "^2.2.1", - "@finos/perspective-viewer-d3fc": "^2.2.1", - "@finos/perspective-viewer-datagrid": "^2.2.1", - "@finos/perspective-viewer-openlayers": "^2.2.1", + "@finos/perspective": "^2.3.0", + "@finos/perspective-viewer": "^2.3.0", + "@finos/perspective-viewer-d3fc": "^2.3.0", + "@finos/perspective-viewer-datagrid": "^2.3.0", + "@finos/perspective-viewer-openlayers": "^2.3.0", "superstore-arrow": "^1.0.0" }, "devDependencies": { - "@finos/perspective-esbuild-plugin": "^2.2.1", + "@finos/perspective-esbuild-plugin": "^2.3.0", "esbuild": "^0.14.54", "http-server": "^0.11.1" } diff --git a/examples/esbuild-remote/package.json b/examples/esbuild-remote/package.json index 6871ca0816..b37fa24d87 100644 --- a/examples/esbuild-remote/package.json +++ b/examples/esbuild-remote/package.json @@ -1,7 +1,7 @@ { "name": "esbuild-remote", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "An example of 2 Perspectives, one client and one server, streaming via Apache Arrow.", "scripts": { "start": "node build.js && node server/index.mjs" @@ -9,15 +9,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.2.1", - "@finos/perspective-viewer": "^2.2.1", - "@finos/perspective-viewer-d3fc": "^2.2.1", - "@finos/perspective-viewer-datagrid": "^2.2.1", + "@finos/perspective": "^2.3.0", + "@finos/perspective-viewer": "^2.3.0", + "@finos/perspective-viewer-d3fc": "^2.3.0", + "@finos/perspective-viewer-datagrid": "^2.3.0", "express": "^4.17.1", "express-ws": "^5.0.2" }, "devDependencies": { - "@finos/perspective-esbuild-plugin": "^2.2.1", + "@finos/perspective-esbuild-plugin": "^2.3.0", "esbuild": "^0.14.54" } } diff --git a/examples/git-history/package.json b/examples/git-history/package.json index dbf97443b5..2b8ff57a57 100644 --- a/examples/git-history/package.json +++ b/examples/git-history/package.json @@ -1,7 +1,7 @@ { "name": "git-history", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "An example of Perspective's own GIT history rendered in Perspective.", "scripts": { "start": "node server.js" @@ -9,9 +9,9 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.2.1", - "@finos/perspective-viewer": "^2.2.1", - "@finos/perspective-viewer-d3fc": "^2.2.1", - "@finos/perspective-viewer-datagrid": "^2.2.1" + "@finos/perspective": "^2.3.0", + "@finos/perspective-viewer": "^2.3.0", + "@finos/perspective-viewer-d3fc": "^2.3.0", + "@finos/perspective-viewer-datagrid": "^2.3.0" } } diff --git a/examples/promo/package.json b/examples/promo/package.json index cdd84a2a1b..1a3ab90e2c 100644 --- a/examples/promo/package.json +++ b/examples/promo/package.json @@ -1,7 +1,7 @@ { "name": "promo", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "An puppeteer-guided demo of Perspective's functionality, as seen on Github.", "scripts": { "dev": "webpack-dev-server --open", @@ -13,14 +13,14 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.2.1", - "@finos/perspective-viewer": "^2.2.1", - "@finos/perspective-viewer-d3fc": "^2.2.1", - "@finos/perspective-viewer-datagrid": "^2.2.1", - "@finos/perspective-workspace": "^2.2.1" + "@finos/perspective": "^2.3.0", + "@finos/perspective-viewer": "^2.3.0", + "@finos/perspective-viewer-d3fc": "^2.3.0", + "@finos/perspective-viewer-datagrid": "^2.3.0", + "@finos/perspective-workspace": "^2.3.0" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.2.1", + "@finos/perspective-webpack-plugin": "^2.3.0", "http-server": "^0.11.1", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" diff --git a/examples/python-aiohttp/package.json b/examples/python-aiohttp/package.json index 4a88ef4859..e1914b6340 100644 --- a/examples/python-aiohttp/package.json +++ b/examples/python-aiohttp/package.json @@ -1,7 +1,7 @@ { "name": "python-aiohttp", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "An example of editing a `perspective-python` server from the browser.", "scripts": { "start": "PYTHONPATH=../../python/perspective python3 server.py" @@ -9,15 +9,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.2.1", - "@finos/perspective-viewer": "^2.2.1", - "@finos/perspective-viewer-d3fc": "^2.2.1", - "@finos/perspective-viewer-datagrid": "^2.2.1", - "@finos/perspective-workspace": "^2.2.1", + "@finos/perspective": "^2.3.0", + "@finos/perspective-viewer": "^2.3.0", + "@finos/perspective-viewer-d3fc": "^2.3.0", + "@finos/perspective-viewer-datagrid": "^2.3.0", + "@finos/perspective-workspace": "^2.3.0", "superstore-arrow": "^1.0.0" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.2.1", + "@finos/perspective-webpack-plugin": "^2.3.0", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" } diff --git a/examples/python-starlette/package.json b/examples/python-starlette/package.json index 6f2de0ddef..4ec8acce40 100644 --- a/examples/python-starlette/package.json +++ b/examples/python-starlette/package.json @@ -1,7 +1,7 @@ { "name": "python-starlette", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "An example of editing a `perspective-python` server from the browser.", "scripts": { "start": "PYTHONPATH=../../python/perspective python3 server.py" @@ -9,15 +9,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.2.1", - "@finos/perspective-viewer": "^2.2.1", - "@finos/perspective-viewer-d3fc": "^2.2.1", - "@finos/perspective-viewer-datagrid": "^2.2.1", - "@finos/perspective-workspace": "^2.2.1", + "@finos/perspective": "^2.3.0", + "@finos/perspective-viewer": "^2.3.0", + "@finos/perspective-viewer-d3fc": "^2.3.0", + "@finos/perspective-viewer-datagrid": "^2.3.0", + "@finos/perspective-workspace": "^2.3.0", "superstore-arrow": "^1.0.0" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.2.1", + "@finos/perspective-webpack-plugin": "^2.3.0", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" } diff --git a/examples/python-tornado-streaming/index.html b/examples/python-tornado-streaming/index.html index 363829ae1e..0a7a24dfc4 100644 --- a/examples/python-tornado-streaming/index.html +++ b/examples/python-tornado-streaming/index.html @@ -12,11 +12,11 @@ - - - + + + - +