MDEV-23370 innodb_fts.innodb_fts_misc failed in buildbot, server crashed in...

MDEV-23370 innodb_fts.innodb_fts_misc failed in buildbot, server crashed in dict_table_autoinc_destroy

This issue is caused by MDEV-22456 ad6171b9. Fix involves the backported version of 10.4 patch
MDEV-22778 5f2628d1 and few parts of
MDEV-17441 (e9a5f288).

dict_table_t::stats_latch_created: Removed

dict_table_t::stats_latch: make value member and always lock it for
simplicity even for stats cloned table.

zip_pad_info_t::mutex_created: Removed

zip_pad_info_t::mutex: make member value instead of pointer

os0once.h: Removed

dict_table_remove_from_cache_low(): Ensure that fts_free() is always
called, even if dict_mem_table_free() is deferred until
btr_search_lazy_free().

InnoDB would always zip_pad_info_t::mutex and
dict_table_t::autoinc_mutex, even for tables are not in
ROW_FORMAT=COMPRESSED nor include any AUTO_INCREMENT column.
parent 987df9b3
......@@ -60,7 +60,6 @@ extern uint ibuf_debug;
#include "lock0lock.h"
#include "mach0data.h"
#include "mem0mem.h"
#include "os0once.h"
#include "page0page.h"
#include "page0zip.h"
#include "pars0pars.h"
......@@ -268,76 +267,6 @@ dict_mutex_exit_for_mysql(void)
mutex_exit(&dict_sys->mutex);
}
/** Allocate and init a dict_table_t's stats latch.
This function must not be called concurrently on the same table object.
@param[in,out] table_void table whose stats latch to create */
static
void
dict_table_stats_latch_alloc(
void* table_void)
{
dict_table_t* table = static_cast<dict_table_t*>(table_void);
/* Note: rw_lock_create() will call the constructor */
table->stats_latch = static_cast<rw_lock_t*>(
ut_malloc_nokey(sizeof(rw_lock_t)));
ut_a(table->stats_latch != NULL);
rw_lock_create(dict_table_stats_key, table->stats_latch,
SYNC_INDEX_TREE);
}
/** Deinit and free a dict_table_t's stats latch.
This function must not be called concurrently on the same table object.
@param[in,out] table table whose stats latch to free */
static
void
dict_table_stats_latch_free(
dict_table_t* table)
{
rw_lock_free(table->stats_latch);
ut_free(table->stats_latch);
}
/** Create a dict_table_t's stats latch or delay for lazy creation.
This function is only called from either single threaded environment
or from a thread that has not shared the table object with other threads.
@param[in,out] table table whose stats latch to create
@param[in] enabled if false then the latch is disabled
and dict_table_stats_lock()/unlock() become noop on this table. */
void
dict_table_stats_latch_create(
dict_table_t* table,
bool enabled)
{
if (!enabled) {
table->stats_latch = NULL;
table->stats_latch_created = os_once::DONE;
return;
}
/* We create this lazily the first time it is used. */
table->stats_latch = NULL;
table->stats_latch_created = os_once::NEVER_DONE;
}
/** Destroy a dict_table_t's stats latch.
This function is only called from either single threaded environment
or from a thread that has not shared the table object with other threads.
@param[in,out] table table whose stats latch to destroy */
void
dict_table_stats_latch_destroy(
dict_table_t* table)
{
if (table->stats_latch_created == os_once::DONE
&& table->stats_latch != NULL) {
dict_table_stats_latch_free(table);
}
}
/** Lock the appropriate latch to protect a given table's statistics.
@param[in] table table whose stats to lock
@param[in] latch_mode RW_S_LATCH or RW_X_LATCH */
......@@ -349,23 +278,12 @@ dict_table_stats_lock(
ut_ad(table != NULL);
ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
os_once::do_or_wait_for_done(
&table->stats_latch_created,
dict_table_stats_latch_alloc, table);
if (table->stats_latch == NULL) {
/* This is a dummy table object that is private in the current
thread and is not shared between multiple threads, thus we
skip any locking. */
return;
}
switch (latch_mode) {
case RW_S_LATCH:
rw_lock_s_lock(table->stats_latch);
rw_lock_s_lock(&table->stats_latch);
break;
case RW_X_LATCH:
rw_lock_x_lock(table->stats_latch);
rw_lock_x_lock(&table->stats_latch);
break;
case RW_NO_LATCH:
/* fall through */
......@@ -385,19 +303,12 @@ dict_table_stats_unlock(
ut_ad(table != NULL);
ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
if (table->stats_latch == NULL) {
/* This is a dummy table object that is private in the current
thread and is not shared between multiple threads, thus we
skip any locking. */
return;
}
switch (latch_mode) {
case RW_S_LATCH:
rw_lock_s_unlock(table->stats_latch);
rw_lock_s_unlock(&table->stats_latch);
break;
case RW_X_LATCH:
rw_lock_x_unlock(table->stats_latch);
rw_lock_x_unlock(&table->stats_latch);
break;
case RW_NO_LATCH:
/* fall through */
......@@ -737,34 +648,6 @@ dict_table_get_nth_v_col_mysql(
return(dict_table_get_nth_v_col(table, i));
}
/** Allocate and init the autoinc latch of a given table.
This function must not be called concurrently on the same table object.
@param[in,out] table_void table whose autoinc latch to create */
static
void
dict_table_autoinc_alloc(
void* table_void)
{
dict_table_t* table = static_cast<dict_table_t*>(table_void);
table->autoinc_mutex = UT_NEW_NOKEY(ib_mutex_t());
ut_a(table->autoinc_mutex != NULL);
mutex_create(LATCH_ID_AUTOINC, table->autoinc_mutex);
}
/** Allocate and init the zip_pad_mutex of a given index.
This function must not be called concurrently on the same index object.
@param[in,out] index_void index whose zip_pad_mutex to create */
static
void
dict_index_zip_pad_alloc(
void* index_void)
{
dict_index_t* index = static_cast<dict_index_t*>(index_void);
index->zip_pad.mutex = UT_NEW_NOKEY(SysMutex());
ut_a(index->zip_pad.mutex != NULL);
mutex_create(LATCH_ID_ZIP_PAD_MUTEX, index->zip_pad.mutex);
}
/********************************************************************//**
Acquire the autoinc lock. */
void
......@@ -772,11 +655,7 @@ dict_table_autoinc_lock(
/*====================*/
dict_table_t* table) /*!< in/out: table */
{
os_once::do_or_wait_for_done(
&table->autoinc_mutex_created,
dict_table_autoinc_alloc, table);
mutex_enter(table->autoinc_mutex);
mysql_mutex_lock(&table->autoinc_mutex);
}
/** Acquire the zip_pad_mutex latch.
......@@ -786,11 +665,7 @@ void
dict_index_zip_pad_lock(
dict_index_t* index)
{
os_once::do_or_wait_for_done(
&index->zip_pad.mutex_created,
dict_index_zip_pad_alloc, index);
mutex_enter(index->zip_pad.mutex);
mysql_mutex_lock(&index->zip_pad.mutex);
}
/** Get all the FTS indexes on a table.
......@@ -825,7 +700,7 @@ dict_table_autoinc_unlock(
/*======================*/
dict_table_t* table) /*!< in/out: table */
{
mutex_exit(table->autoinc_mutex);
mysql_mutex_unlock(&table->autoinc_mutex);
}
/** Looks for column n in an index.
......@@ -1276,6 +1151,8 @@ dict_table_add_to_cache(
dict_table_add_system_columns(table, heap);
mysql_mutex_init(0, &table->autoinc_mutex, NULL);
table->cached = TRUE;
fold = ut_fold_string(table->name.m_name);
......@@ -1419,7 +1296,7 @@ dict_index_t *dict_index_t::clone() const
(mem_heap_zalloc(heap, n_uniq * sizeof *stat_n_sample_sizes));
index->stat_n_non_null_key_vals= static_cast<ib_uint64_t*>
(mem_heap_zalloc(heap, n_uniq * sizeof *stat_n_non_null_key_vals));
memset(&index->zip_pad, 0, sizeof index->zip_pad);
mysql_mutex_init(0, &index->zip_pad.mutex, NULL);
return index;
}
......@@ -2133,8 +2010,15 @@ dict_table_remove_from_cache_low(
UT_DELETE(table->vc_templ);
}
mysql_mutex_destroy(&table->autoinc_mutex);
#ifdef BTR_CUR_HASH_ADAPT
if (UNIV_UNLIKELY(UT_LIST_GET_LEN(table->freed_indexes) != 0)) {
if (table->fts) {
fts_optimize_remove_table(table);
fts_free(table);
table->fts = NULL;
}
table->vc_templ = NULL;
table->id = 0;
return;
......
......@@ -118,20 +118,15 @@ static bool dict_mem_table_is_system(char *name)
}
}
/**********************************************************************//**
Creates a table memory object.
@return own: table object */
dict_table_t*
dict_mem_table_create(
/*==================*/
const char* name, /*!< in: table name */
ulint space, /*!< in: space where the clustered index of
the table is placed */
ulint n_cols, /*!< in: total number of columns including
virtual and non-virtual columns */
ulint n_v_cols,/*!< in: number of virtual columns */
ulint flags, /*!< in: table flags */
ulint flags2) /*!< in: table flags2 */
const char* name,
ulint space,
ulint n_cols,
ulint n_v_cols,
ulint flags,
ulint flags2,
bool init_stats_latch)
{
dict_table_t* table;
mem_heap_t* heap;
......@@ -170,16 +165,9 @@ dict_mem_table_create(
table->v_cols = static_cast<dict_v_col_t*>(
mem_heap_alloc(heap, n_v_cols * sizeof(*table->v_cols)));
/* true means that the stats latch will be enabled -
dict_table_stats_lock() will not be noop. */
dict_table_stats_latch_create(table, true);
table->autoinc_lock = static_cast<ib_lock_t*>(
mem_heap_alloc(heap, lock_get_size()));
/* lazy creation of table autoinc latch */
dict_table_autoinc_create_lazy(table);
/* If the table has an FTS index or we are in the process
of building one, create the table->fts */
if (dict_table_has_fts_index(table)
......@@ -194,6 +182,12 @@ dict_mem_table_create(
new(&table->foreign_set) dict_foreign_set();
new(&table->referenced_set) dict_foreign_set();
if (init_stats_latch) {
rw_lock_create(dict_table_stats_key, &table->stats_latch,
SYNC_INDEX_TREE);
table->stats_latch_inited = true;
}
return(table);
}
......@@ -222,9 +216,7 @@ dict_mem_table_free(
}
}
dict_table_autoinc_destroy(table);
dict_mem_table_free_foreign_vcol_set(table);
dict_table_stats_latch_destroy(table);
table->foreign_set.~dict_foreign_set();
table->referenced_set.~dict_foreign_set();
......@@ -245,6 +237,10 @@ dict_mem_table_free(
UT_DELETE(table->s_cols);
}
if (table->stats_latch_inited) {
rw_lock_free(&table->stats_latch);
}
mem_heap_free(table->heap);
}
......@@ -767,7 +763,7 @@ dict_mem_index_create(
dict_mem_fill_index_struct(index, heap, table_name, index_name,
space, type, n_fields);
dict_index_zip_pad_mutex_create_lazy(index);
mysql_mutex_init(0, &index->zip_pad.mutex, NULL);
if (type & DICT_SPATIAL) {
index->rtr_track = static_cast<rtr_info_track_t*>(
......@@ -1082,7 +1078,7 @@ dict_mem_index_free(
ut_ad(index);
ut_ad(index->magic_n == DICT_INDEX_MAGIC_N);
dict_index_zip_pad_mutex_destroy(index);
mysql_mutex_destroy(&index->zip_pad.mutex);
if (dict_index_is_spatial(index)) {
rtr_info_active::iterator it;
......
......@@ -33,6 +33,7 @@ Created Jan 06, 2010 Vasil Dimov
#include "pars0pars.h"
#include <mysql_com.h>
#include "btr0btr.h"
#include "sync0sync.h"
#include <algorithm>
#include <map>
......@@ -418,11 +419,6 @@ dict_stats_table_clone_create(
t->corrupted = table->corrupted;
/* This private object "t" is not shared with other threads, so
we do not need the stats_latch (thus we pass false below). The
dict_table_stats_lock()/unlock() routines will do nothing. */
dict_table_stats_latch_create(t, false);
UT_LIST_INIT(t->indexes, &dict_index_t::indexes);
#ifdef BTR_CUR_HASH_ADAPT
UT_LIST_INIT(t->freed_indexes, &dict_index_t::indexes);
......@@ -490,6 +486,8 @@ dict_stats_table_clone_create(
ut_d(t->magic_n = DICT_TABLE_MAGIC_N);
rw_lock_create(dict_table_stats_key, &t->stats_latch, SYNC_INDEX_TREE);
return(t);
}
......@@ -502,7 +500,7 @@ dict_stats_table_clone_free(
/*========================*/
dict_table_t* t) /*!< in: dummy table object to free */
{
dict_table_stats_latch_destroy(t);
rw_lock_free(&t->stats_latch);
mem_heap_free(t->heap);
}
......
......@@ -1447,7 +1447,7 @@ ibuf_dummy_index_create(
table = dict_mem_table_create("IBUF_DUMMY",
DICT_HDR_SPACE, n, 0,
comp ? DICT_TF_COMPACT : 0, 0);
comp ? DICT_TF_COMPACT : 0, 0, false);
index = dict_mem_index_create("IBUF_DUMMY", "IBUF_DUMMY",
DICT_HDR_SPACE, 0, n);
......
......@@ -302,7 +302,7 @@ UNIV_INLINE
void
dict_table_autoinc_initialize(dict_table_t* table, ib_uint64_t value)
{
ut_ad(dict_table_autoinc_own(table));
mysql_mutex_assert_owner(&table->autoinc_mutex);
table->autoinc = value;
}
......@@ -315,7 +315,7 @@ UNIV_INLINE
ib_uint64_t
dict_table_autoinc_read(const dict_table_t* table)
{
ut_ad(dict_table_autoinc_own(table));
mysql_mutex_assert_owner(&table->autoinc_mutex);
return(table->autoinc);
}
......@@ -329,7 +329,7 @@ UNIV_INLINE
bool
dict_table_autoinc_update_if_greater(dict_table_t* table, ib_uint64_t value)
{
ut_ad(dict_table_autoinc_own(table));
mysql_mutex_assert_owner(&table->autoinc_mutex);
if (value > table->autoinc) {
......@@ -1524,25 +1524,6 @@ void
dict_mutex_exit_for_mysql(void);
/*===========================*/
/** Create a dict_table_t's stats latch or delay for lazy creation.
This function is only called from either single threaded environment
or from a thread that has not shared the table object with other threads.
@param[in,out] table table whose stats latch to create
@param[in] enabled if false then the latch is disabled
and dict_table_stats_lock()/unlock() become noop on this table. */
void
dict_table_stats_latch_create(
dict_table_t* table,
bool enabled);
/** Destroy a dict_table_t's stats latch.
This function is only called from either single threaded environment
or from a thread that has not shared the table object with other threads.
@param[in,out] table table whose stats latch to destroy */
void
dict_table_stats_latch_destroy(
dict_table_t* table);
/** Lock the appropriate latch to protect a given table's statistics.
@param[in] table table whose stats to lock
@param[in] latch_mode RW_S_LATCH or RW_X_LATCH */
......
......@@ -44,7 +44,6 @@ Created 1/8/1996 Heikki Tuuri
#include "fts0fts.h"
#include "buf0buf.h"
#include "gis0type.h"
#include "os0once.h"
#include "fil0fil.h"
#include <my_crypt.h>
#include "fil0crypt.h"
......@@ -299,21 +298,27 @@ parent table will fail, and user has to drop excessive foreign constraint
before proceeds. */
#define FK_MAX_CASCADE_DEL 15
/**********************************************************************//**
Creates a table memory object.
/** Creates a table memory object.
@param[in] name table name
@param[in] space space where the clustered index
of the table is placed
@param[in] n_cols total number of columns including
virtual and non-virtual columns
@param[in] n_v_cols number of virtual columns
@param[in] flags table flags
@param[in] flags2 table flags2
@param[in] init_stats_latch whether to init the stats latch
@return own: table object */
dict_table_t*
dict_mem_table_create(
/*==================*/
const char* name, /*!< in: table name */
ulint space, /*!< in: space where the clustered index
of the table is placed */
ulint n_cols, /*!< in: total number of columns
including virtual and non-virtual
columns */
ulint n_v_cols, /*!< in: number of virtual columns */
ulint flags, /*!< in: table flags */
ulint flags2); /*!< in: table flags2 */
const char* name,
ulint space,
ulint n_cols,
ulint n_v_cols,
ulint flags,
ulint flags2,
bool init_stats_latch=true);
/****************************************************************//**
Free a table memory object. */
void
......@@ -792,7 +797,7 @@ extern ulong zip_pad_max;
an uncompressed page should be left as padding to avoid compression
failures. This estimate is based on a self-adapting heuristic. */
struct zip_pad_info_t {
SysMutex* mutex; /*!< mutex protecting the info */
mysql_mutex_t mutex; /*!< mutex protecting the info */
ulint pad; /*!< number of bytes used as pad */
ulint success;/*!< successful compression ops during
current round */
......@@ -800,9 +805,6 @@ struct zip_pad_info_t {
current round */
ulint n_rounds;/*!< number of currently successful
rounds */
volatile os_once::state_t
mutex_created;
/*!< Creation state of mutex member */
};
/** Number of samples of data size kept when page compression fails for
......@@ -1692,7 +1694,7 @@ struct dict_table_t {
/** Statistics for query optimization. @{ */
/** Creation state of 'stats_latch'. */
volatile os_once::state_t stats_latch_created;
bool stats_latch_inited;
/** This latch protects:
dict_table_t::stat_initialized,
......@@ -1705,7 +1707,7 @@ struct dict_table_t {
dict_table_t::indexes*::stat_n_leaf_pages.
(*) Those are not always protected for
performance reasons. */
rw_lock_t* stats_latch;
rw_lock_t stats_latch;
/** TRUE if statistics have been calculated the first time after
database startup or table creation. */
......@@ -1829,11 +1831,8 @@ struct dict_table_t {
from a select. */
lock_t* autoinc_lock;
/** Creation state of autoinc_mutex member */
volatile os_once::state_t autoinc_mutex_created;
/** Mutex protecting the autoincrement counter. */
ib_mutex_t* autoinc_mutex;
mysql_mutex_t autoinc_mutex;
/** Autoinc counter value to give to the next inserted row. */
ib_uint64_t autoinc;
......@@ -1927,64 +1926,6 @@ struct dict_foreign_add_to_referenced_table {
}
};
/** Destroy the autoinc latch of the given table.
This function is only called from either single threaded environment
or from a thread that has not shared the table object with other threads.
@param[in,out] table table whose stats latch to destroy */
inline
void
dict_table_autoinc_destroy(
dict_table_t* table)
{
if (table->autoinc_mutex_created == os_once::DONE
&& table->autoinc_mutex != NULL) {
mutex_free(table->autoinc_mutex);
UT_DELETE(table->autoinc_mutex);
}
}
/** Request for lazy creation of the autoinc latch of a given table.
This function is only called from either single threaded environment
or from a thread that has not shared the table object with other threads.
@param[in,out] table table whose autoinc latch is to be created. */
inline
void
dict_table_autoinc_create_lazy(
dict_table_t* table)
{
table->autoinc_mutex = NULL;
table->autoinc_mutex_created = os_once::NEVER_DONE;
}
/** Request a lazy creation of dict_index_t::zip_pad::mutex.
This function is only called from either single threaded environment
or from a thread that has not shared the table object with other threads.
@param[in,out] index index whose zip_pad mutex is to be created */
inline
void
dict_index_zip_pad_mutex_create_lazy(
dict_index_t* index)
{
index->zip_pad.mutex = NULL;
index->zip_pad.mutex_created = os_once::NEVER_DONE;
}
/** Destroy the zip_pad_mutex of the given index.
This function is only called from either single threaded environment
or from a thread that has not shared the table object with other threads.
@param[in,out] table table whose stats latch to destroy */
inline
void
dict_index_zip_pad_mutex_destroy(
dict_index_t* index)
{
if (index->zip_pad.mutex_created == os_once::DONE
&& index->zip_pad.mutex != NULL) {
mutex_free(index->zip_pad.mutex);
UT_DELETE(index->zip_pad.mutex);
}
}
/** Release the zip_pad_mutex of a given index.
@param[in,out] index index whose zip_pad_mutex is to be released */
inline
......@@ -1992,21 +1933,8 @@ void
dict_index_zip_pad_unlock(
dict_index_t* index)
{
mutex_exit(index->zip_pad.mutex);
}
#ifdef UNIV_DEBUG
/** Check if the current thread owns the autoinc_mutex of a given table.
@param[in] table the autoinc_mutex belongs to this table
@return true, if the current thread owns the autoinc_mutex, false otherwise.*/
inline
bool
dict_table_autoinc_own(
const dict_table_t* table)
{
return(mutex_own(table->autoinc_mutex));
mysql_mutex_unlock(&index->zip_pad.mutex);
}
#endif /* UNIV_DEBUG */
/** Check whether the col is used in spatial index or regular index.
@param[in] col column to check
......
/*****************************************************************************
Copyright (c) 2014, Oracle and/or its affiliates. All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
*****************************************************************************/
/**************************************************//**
@file include/os0once.h
A class that aids executing a given function exactly once in a multi-threaded
environment.
Created Feb 20, 2014 Vasil Dimov
*******************************************************/
#ifndef os0once_h
#define os0once_h
#include "univ.i"
#include "ut0ut.h"
/** Execute a given function exactly once in a multi-threaded environment
or wait for the function to be executed by another thread.
Example usage:
First the user must create a control variable of type os_once::state_t and
assign it os_once::NEVER_DONE.
Then the user must pass this variable, together with a function to be
executed to os_once::do_or_wait_for_done().
Multiple threads can call os_once::do_or_wait_for_done() simultaneously with
the same (os_once::state_t) control variable. The provided function will be
called exactly once and when os_once::do_or_wait_for_done() returns then this
function has completed execution, by this or another thread. In other words
os_once::do_or_wait_for_done() will either execute the provided function or
will wait for its execution to complete if it is already called by another
thread or will do nothing if the function has already completed its execution
earlier.
This mimics pthread_once(3), but unfortunatelly pthread_once(3) does not
support passing arguments to the init_routine() function. We should use
std::call_once() when we start compiling with C++11 enabled. */
class os_once {
public:
/** Control variables' state type */
typedef ib_uint32_t state_t;
/** Not yet executed. */
static const state_t NEVER_DONE = 0;
/** Currently being executed by this or another thread. */
static const state_t IN_PROGRESS = 1;
/** Finished execution. */
static const state_t DONE = 2;
/** Call a given function or wait its execution to complete if it is
already called by another thread.
@param[in,out] state control variable
@param[in] do_func function to call
@param[in,out] do_func_arg an argument to pass to do_func(). */
static
void
do_or_wait_for_done(
volatile state_t* state,
void (*do_func)(void*),
void* do_func_arg)
{
int32 oldval = NEVER_DONE;
/* Avoid calling my_atomic_cas32() in the most common case. */
if (*state == DONE) {
return;
}
if (my_atomic_cas32((int32*) state, &oldval, IN_PROGRESS)) {
/* We are the first. Call the function. */
do_func(do_func_arg);
my_atomic_store32((int32*) state, DONE);
} else {
/* The state is not NEVER_DONE, so either it is
IN_PROGRESS (somebody is calling the function right
now or DONE (it has already been called and completed).
Wait for it to become DONE. */
for (;;) {
const state_t s = *state;
switch (s) {
case DONE:
return;
case IN_PROGRESS:
break;
case NEVER_DONE:
/* fall through */
default:
ut_error;
}
UT_RELAX_CPU();
}
}
}
};
#endif /* os0once_h */
......@@ -1656,7 +1656,7 @@ page_zip_fields_free(
{
if (index) {
dict_table_t* table = index->table;
dict_index_zip_pad_mutex_destroy(index);
mysql_mutex_destroy(&index->zip_pad.mutex);
mem_heap_free(index->heap);
dict_mem_table_free(table);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment