Path: blob/master/src/hotspot/share/utilities/concurrentHashTable.hpp
40949 views
/*1* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*22*/2324#ifndef SHARE_UTILITIES_CONCURRENTHASHTABLE_HPP25#define SHARE_UTILITIES_CONCURRENTHASHTABLE_HPP2627#include "memory/allocation.hpp"28#include "utilities/globalCounter.hpp"29#include "utilities/globalDefinitions.hpp"30#include "utilities/tableStatistics.hpp"3132// A mostly concurrent-hash-table where the read-side is wait-free, inserts are33// CAS and deletes mutual exclude each other on per bucket-basis. VALUE is the34// type kept inside each Node and CONFIG contains hash and allocation methods.35// A CALLBACK_FUNC and LOOKUP_FUNC needs to be provided for get and insert.3637class Thread;38class Mutex;3940template <typename CONFIG, MEMFLAGS F>41class ConcurrentHashTable : public CHeapObj<F> {42typedef typename CONFIG::Value VALUE;43private:44// This is the internal node structure.45// Only constructed with placement new from memory allocated with MEMFLAGS of46// the InternalTable or user-defined memory.47class Node {48private:49Node * volatile _next;50VALUE _value;51public:52Node(const VALUE& value, Node* next = NULL)53: _next(next), _value(value) {54assert((((uintptr_t)this) & ((uintptr_t)0x3)) == 0,55"Must 16 bit aligned.");56}5758Node* next() const;59void set_next(Node* node) { _next = node; }60Node* const volatile * next_ptr() { return &_next; }6162VALUE* value() { return &_value; }6364// Creates a node.65static Node* create_node(void* context, const VALUE& value, Node* next = NULL) {66return new (CONFIG::allocate_node(context, sizeof(Node), value)) Node(value, next);67}68// Destroys a node.69static void destroy_node(void* context, Node* node) {70CONFIG::free_node(context, (void*)node, node->_value);71}7273void print_on(outputStream* st) const {};74void print_value_on(outputStream* st) const {};75};7677// Only constructed with placement new from an array allocated with MEMFLAGS78// of InternalTable.79class Bucket {80private:8182// Embedded state in two low bits in first pointer is a spinlock with 383// states, unlocked, locked, redirect. You must never busy-spin on trylock()84// or call lock() without _resize_lock, that would deadlock. Redirect can85// only be installed by owner and is the final state of a bucket.86// The only two valid flows are:87// unlocked -> locked -> unlocked88// unlocked -> locked -> redirect89// Locked state only applies to an updater.90// Reader only check for redirect.91Node * volatile _first;9293static const uintptr_t STATE_LOCK_BIT = 0x1;94static const uintptr_t STATE_REDIRECT_BIT = 0x2;95static const uintptr_t STATE_MASK = 0x3;9697// Get the first pointer unmasked.98Node* first_raw() const;99100// Methods to manipulate the embedded.101static bool is_state(Node* node, uintptr_t bits) {102return (bits & (uintptr_t)node) == bits;103}104105static Node* set_state(Node* n, uintptr_t bits) {106return (Node*)(bits | (uintptr_t)n);107}108109static uintptr_t get_state(Node* node) {110return (((uintptr_t)node) & STATE_MASK);111}112113static Node* clear_state(Node* node) {114return (Node*)(((uintptr_t)node) & (~(STATE_MASK)));115}116117static Node* clear_set_state(Node* node, Node* state) {118return (Node*)(((uintptr_t)clear_state(node)) ^ get_state(state));119}120121public:122// A bucket is only one pointer with the embedded state.123Bucket() : _first(NULL) {};124125// Get the first pointer unmasked.126Node* first() const;127128// Get a pointer to the const first pointer. Do not deference this129// pointer, the pointer pointed to _may_ contain an embedded state. Such130// pointer should only be used as input to release_assign_node_ptr.131Node* const volatile * first_ptr() { return &_first; }132133// This is the only place where a pointer to a Node pointer that potentially134// is _first should be changed. Otherwise we destroy the embedded state. We135// only give out pointer to const Node pointer to avoid accidental136// assignment, thus here we must cast const part away. Method is not static137// due to an assert.138void release_assign_node_ptr(Node* const volatile * dst, Node* node) const;139140// This method assigns this buckets last Node next ptr to input Node.141void release_assign_last_node_next(Node* node);142143// Setting the first pointer must be done with CAS.144bool cas_first(Node *node, Node* expect);145146// Returns true if this bucket is redirecting to a new table.147// Redirect is a terminal state and will never change.148bool have_redirect() const;149150// Return true if this bucket is locked for updates.151bool is_locked() const;152153// Return true if this bucket was locked.154bool trylock();155156// The bucket might be invalid, due to a concurrent resize. The lock()157// method do no respect that and can deadlock if caller do not hold158// _resize_lock.159void lock();160161// Unlocks this bucket.162void unlock();163164// Installs redirect in this bucket.165// Prior to doing so you must have successfully locked this bucket.166void redirect();167};168169// The backing storage table holding the buckets and it's size and mask-bits.170// Table is always a power of two for two reasons:171// - Re-size can only change the size into half or double172// (any pow 2 would also be possible).173// - Use masking of hash for bucket index.174class InternalTable : public CHeapObj<F> {175private:176Bucket* _buckets; // Bucket array.177public:178const size_t _log2_size; // Size in log2.179const size_t _size; // Size in log10.180181// The mask used on hash for selecting bucket.182// The masked value is guaranteed be to inside the buckets array.183const size_t _hash_mask;184185// Create a backing table186InternalTable(size_t log2_size);187~InternalTable();188189Bucket* get_buckets() { return _buckets; }190Bucket* get_bucket(size_t idx) { return &_buckets[idx]; }191};192193// For materializing a supplied value.194class LazyValueRetrieve {195private:196const VALUE& _val;197public:198LazyValueRetrieve(const VALUE& val) : _val(val) {}199const VALUE& operator()() { return _val; }200};201202void* _context;203204InternalTable* _table; // Active table.205InternalTable* _new_table; // Table we are resizing to.206207// Default sizes208static const size_t DEFAULT_MAX_SIZE_LOG2 = 21;209static const size_t DEFAULT_START_SIZE_LOG2 = 13;210static const size_t DEFAULT_GROW_HINT = 4; // Chain length211212const size_t _log2_size_limit; // The biggest size.213const size_t _log2_start_size; // Start size.214const size_t _grow_hint; // Number of linked items215216volatile bool _size_limit_reached;217218// We serialize resizers and other bulk operations which do not support219// concurrent resize with this lock.220Mutex* _resize_lock;221// Since we need to drop mutex for safepoints, but stop other threads from222// taking the mutex after a safepoint this bool is the actual state. After223// acquiring the mutex you must check if this is already locked. If so you224// must drop the mutex until the real lock holder grabs the mutex.225volatile Thread* _resize_lock_owner;226227// Return true if lock mutex/state succeeded.228bool try_resize_lock(Thread* locker);229// Returns when both mutex and state are proper locked.230void lock_resize_lock(Thread* locker);231// Unlocks mutex and state.232void unlock_resize_lock(Thread* locker);233234// This method sets the _invisible_epoch and do a write_synchronize.235// Subsequent calls check the state of _invisible_epoch and determine if the236// write_synchronize can be avoided. If not, it sets the _invisible_epoch237// again and do a write_synchronize.238void write_synchonize_on_visible_epoch(Thread* thread);239// To be-able to avoid write_synchronize in resize and other bulk operation,240// this field keep tracks if a version of the hash-table was ever been seen.241// We the working thread pointer as tag for debugging. The _invisible_epoch242// can only be used by the owner of _resize_lock.243volatile Thread* _invisible_epoch;244245// Scoped critical section, which also handles the invisible epochs.246// An invisible epoch/version do not need a write_synchronize().247class ScopedCS: public StackObj {248protected:249Thread* _thread;250ConcurrentHashTable<CONFIG, F>* _cht;251GlobalCounter::CSContext _cs_context;252public:253ScopedCS(Thread* thread, ConcurrentHashTable<CONFIG, F>* cht);254~ScopedCS();255};256257258// Max number of deletes in one bucket chain during bulk delete.259static const size_t BULK_DELETE_LIMIT = 256;260261// Simple getters and setters for the internal table.262InternalTable* get_table() const;263InternalTable* get_new_table() const;264InternalTable* set_table_from_new();265266// Destroys all nodes.267void free_nodes();268269// Mask away high bits of hash.270static size_t bucket_idx_hash(InternalTable* table, const uintx hash) {271return ((size_t)hash) & table->_hash_mask;272}273274// Returns bucket for hash for that internal table.275Bucket* get_bucket_in(InternalTable* table, const uintx hash) const {276size_t bucket_index = bucket_idx_hash(table, hash);277return table->get_bucket(bucket_index);278}279280// Return correct bucket for reading and handles resizing.281Bucket* get_bucket(const uintx hash) const;282283// Return correct bucket for updates and handles resizing.284Bucket* get_bucket_locked(Thread* thread, const uintx hash);285286// Finds a node.287template <typename LOOKUP_FUNC>288Node* get_node(const Bucket* const bucket, LOOKUP_FUNC& lookup_f,289bool* have_dead, size_t* loops = NULL) const;290291// Method for shrinking.292bool internal_shrink_prolog(Thread* thread, size_t log2_size);293void internal_shrink_epilog(Thread* thread);294void internal_shrink_range(Thread* thread, size_t start, size_t stop);295bool internal_shrink(Thread* thread, size_t size_limit_log2);296void internal_reset(size_t log2_size);297298// Methods for growing.299bool unzip_bucket(Thread* thread, InternalTable* old_table,300InternalTable* new_table, size_t even_index,301size_t odd_index);302bool internal_grow_prolog(Thread* thread, size_t log2_size);303void internal_grow_epilog(Thread* thread);304void internal_grow_range(Thread* thread, size_t start, size_t stop);305bool internal_grow(Thread* thread, size_t log2_size);306307// Get a value.308template <typename LOOKUP_FUNC>309VALUE* internal_get(Thread* thread, LOOKUP_FUNC& lookup_f,310bool* grow_hint = NULL);311312// Insert and get current value.313template <typename LOOKUP_FUNC, typename FOUND_FUNC>314bool internal_insert_get(Thread* thread, LOOKUP_FUNC& lookup_f, const VALUE& value,315FOUND_FUNC& foundf, bool* grow_hint, bool* clean_hint);316317// Returns true if an item matching LOOKUP_FUNC is removed.318// Calls DELETE_FUNC before destroying the node.319template <typename LOOKUP_FUNC, typename DELETE_FUNC>320bool internal_remove(Thread* thread, LOOKUP_FUNC& lookup_f,321DELETE_FUNC& delete_f);322323// Visits nodes with FUNC.324template <typename FUNC>325static bool visit_nodes(Bucket* bucket, FUNC& visitor_f);326327// During shrink/grow we cannot guarantee that we only visit nodes once, with328// current algorithm. To keep it simple caller will have locked329// _resize_lock.330template <typename FUNC>331void do_scan_locked(Thread* thread, FUNC& scan_f);332333// Check for dead items in a bucket.334template <typename EVALUATE_FUNC>335size_t delete_check_nodes(Bucket* bucket, EVALUATE_FUNC& eval_f,336size_t num_del, Node** ndel);337338// Check for dead items in this table. During shrink/grow we cannot guarantee339// that we only visit nodes once. To keep it simple caller will have locked340// _resize_lock.341template <typename EVALUATE_FUNC, typename DELETE_FUNC>342void do_bulk_delete_locked(Thread* thread, EVALUATE_FUNC& eval_f343, DELETE_FUNC& del_f) {344do_bulk_delete_locked_for(thread, 0, _table->_size, eval_f, del_f);345}346347// To have prefetching for a VALUE that is pointer during348// do_bulk_delete_locked, we have this helper classes. One for non-pointer349// case without prefect and one for pointer with prefect.350template <bool b, typename EVALUATE_FUNC>351struct HaveDeletables {352static bool have_deletable(Bucket* bucket, EVALUATE_FUNC& eval_f,353Bucket* prefetch_bucket);354};355template<typename EVALUATE_FUNC>356struct HaveDeletables<true, EVALUATE_FUNC> {357static bool have_deletable(Bucket* bucket, EVALUATE_FUNC& eval_f,358Bucket* prefetch_bucket);359};360361// Check for dead items in this table with range. During shrink/grow we cannot362// guarantee that we only visit nodes once. To keep it simple caller will363// have locked _resize_lock.364template <typename EVALUATE_FUNC, typename DELETE_FUNC>365void do_bulk_delete_locked_for(Thread* thread, size_t start_idx,366size_t stop_idx, EVALUATE_FUNC& eval_f,367DELETE_FUNC& del_f, bool is_mt = false);368369// Method to delete one items.370template <typename LOOKUP_FUNC>371void delete_in_bucket(Thread* thread, Bucket* bucket, LOOKUP_FUNC& lookup_f);372373public:374ConcurrentHashTable(size_t log2size = DEFAULT_START_SIZE_LOG2,375size_t log2size_limit = DEFAULT_MAX_SIZE_LOG2,376size_t grow_hint = DEFAULT_GROW_HINT,377void* context = NULL);378379explicit ConcurrentHashTable(void* context, size_t log2size = DEFAULT_START_SIZE_LOG2) :380ConcurrentHashTable(log2size, DEFAULT_MAX_SIZE_LOG2, DEFAULT_GROW_HINT, context) {}381382~ConcurrentHashTable();383384TableRateStatistics _stats_rate;385386size_t get_size_log2(Thread* thread);387size_t get_node_size() const { return sizeof(Node); }388bool is_max_size_reached() { return _size_limit_reached; }389390// This means no paused bucket resize operation is going to resume391// on this table.392bool is_safepoint_safe() { return _resize_lock_owner == NULL; }393394// Re-size operations.395bool shrink(Thread* thread, size_t size_limit_log2 = 0);396bool grow(Thread* thread, size_t size_limit_log2 = 0);397// Unsafe reset and resize the table. This method assumes that we398// want to clear and maybe resize the internal table without the399// overhead of clearing individual items in the table.400void unsafe_reset(size_t size_log2 = 0);401402// All callbacks for get are under critical sections. Other callbacks may be403// under critical section or may have locked parts of table. Calling any404// methods on the table during a callback is not supported.Only MultiGetHandle405// supports multiple gets.406407// Get methods return true on found item with LOOKUP_FUNC and FOUND_FUNC is408// called.409template <typename LOOKUP_FUNC, typename FOUND_FUNC>410bool get(Thread* thread, LOOKUP_FUNC& lookup_f, FOUND_FUNC& foundf,411bool* grow_hint = NULL);412413// Returns true true if the item was inserted, duplicates are found with414// LOOKUP_FUNC.415template <typename LOOKUP_FUNC>416bool insert(Thread* thread, LOOKUP_FUNC& lookup_f, const VALUE& value,417bool* grow_hint = NULL, bool* clean_hint = NULL) {418struct NOP {419void operator()(...) const {}420} nop;421return internal_insert_get(thread, lookup_f, value, nop, grow_hint, clean_hint);422}423424// Returns true if the item was inserted, duplicates are found with425// LOOKUP_FUNC then FOUND_FUNC is called.426template <typename LOOKUP_FUNC, typename FOUND_FUNC>427bool insert_get(Thread* thread, LOOKUP_FUNC& lookup_f, VALUE& value, FOUND_FUNC& foundf,428bool* grow_hint = NULL, bool* clean_hint = NULL) {429return internal_insert_get(thread, lookup_f, value, foundf, grow_hint, clean_hint);430}431432// This does a fast unsafe insert and can thus only be used when there is no433// risk for a duplicates and no other threads uses this table.434bool unsafe_insert(const VALUE& value);435436// Returns true if items was deleted matching LOOKUP_FUNC and437// prior to destruction DELETE_FUNC is called.438template <typename LOOKUP_FUNC, typename DELETE_FUNC>439bool remove(Thread* thread, LOOKUP_FUNC& lookup_f, DELETE_FUNC& del_f) {440return internal_remove(thread, lookup_f, del_f);441}442443// Same without DELETE_FUNC.444template <typename LOOKUP_FUNC>445bool remove(Thread* thread, LOOKUP_FUNC& lookup_f) {446struct {447void operator()(VALUE*) {}448} ignore_del_f;449return internal_remove(thread, lookup_f, ignore_del_f);450}451452// Visit all items with SCAN_FUNC if no concurrent resize. Takes the resize453// lock to avoid concurrent resizes. Else returns false.454template <typename SCAN_FUNC>455bool try_scan(Thread* thread, SCAN_FUNC& scan_f);456457// Visit all items with SCAN_FUNC when the resize lock is obtained.458template <typename SCAN_FUNC>459void do_scan(Thread* thread, SCAN_FUNC& scan_f);460461// Visit all items with SCAN_FUNC without any protection.462// It will assume there is no other thread accessing this463// table during the safepoint. Must be called with VM thread.464template <typename SCAN_FUNC>465void do_safepoint_scan(SCAN_FUNC& scan_f);466467// Destroying items matching EVALUATE_FUNC, before destroying items468// DELETE_FUNC is called, if resize lock is obtained. Else returns false.469template <typename EVALUATE_FUNC, typename DELETE_FUNC>470bool try_bulk_delete(Thread* thread, EVALUATE_FUNC& eval_f,471DELETE_FUNC& del_f);472473// Destroying items matching EVALUATE_FUNC, before destroying items474// DELETE_FUNC is called, when the resize lock is successfully obtained.475template <typename EVALUATE_FUNC, typename DELETE_FUNC>476void bulk_delete(Thread* thread, EVALUATE_FUNC& eval_f, DELETE_FUNC& del_f);477478// Calcuate statistics. Item sizes are calculated with VALUE_SIZE_FUNC.479template <typename VALUE_SIZE_FUNC>480TableStatistics statistics_calculate(Thread* thread, VALUE_SIZE_FUNC& vs_f);481482// Gets statistics if available, if not return old one. Item sizes are calculated with483// VALUE_SIZE_FUNC.484template <typename VALUE_SIZE_FUNC>485TableStatistics statistics_get(Thread* thread, VALUE_SIZE_FUNC& vs_f, TableStatistics old);486487// Writes statistics to the outputStream. Item sizes are calculated with488// VALUE_SIZE_FUNC.489template <typename VALUE_SIZE_FUNC>490void statistics_to(Thread* thread, VALUE_SIZE_FUNC& vs_f, outputStream* st,491const char* table_name);492493// Moves all nodes from this table to to_cht494bool try_move_nodes_to(Thread* thread, ConcurrentHashTable<CONFIG, F>* to_cht);495496// Scoped multi getter.497class MultiGetHandle : private ScopedCS {498public:499MultiGetHandle(Thread* thread, ConcurrentHashTable<CONFIG, F>* cht)500: ScopedCS(thread, cht) {}501// In the MultiGetHandle scope you can lookup items matching LOOKUP_FUNC.502// The VALUEs are safe as long as you never save the VALUEs outside the503// scope, e.g. after ~MultiGetHandle().504template <typename LOOKUP_FUNC>505VALUE* get(LOOKUP_FUNC& lookup_f, bool* grow_hint = NULL);506};507508private:509class BucketsOperation;510511public:512class BulkDeleteTask;513class GrowTask;514};515516#endif // SHARE_UTILITIES_CONCURRENTHASHTABLE_HPP517518519