Commit caa0e9b1 authored by unknown's avatar unknown

Merge abelkin@bk-internal.mysql.com:/home/bk/mysql-maria

into  desktop.sanja.is.com.ua:/home/bell/mysql/bk/work-maria-assert

parents 25bccab9 b51c819c
...@@ -298,7 +298,12 @@ struct st_pagecache_block_link ...@@ -298,7 +298,12 @@ struct st_pagecache_block_link
#endif #endif
KEYCACHE_CONDVAR *condvar; /* condition variable for 'no readers' event */ KEYCACHE_CONDVAR *condvar; /* condition variable for 'no readers' event */
uchar *buffer; /* buffer for the block page */ uchar *buffer; /* buffer for the block page */
PAGECACHE_FILE *write_locker; #ifdef THREAD
pthread_t write_locker;
#else
int write_locker;
#endif
ulonglong last_hit_time; /* timestamp of the last hit */ ulonglong last_hit_time; /* timestamp of the last hit */
WQUEUE WQUEUE
wqueue[COND_SIZE]; /* queues on waiting requests for new/old pages */ wqueue[COND_SIZE]; /* queues on waiting requests for new/old pages */
...@@ -2196,9 +2201,6 @@ static void info_change_lock(PAGECACHE_BLOCK_LINK *block, my_bool wl) ...@@ -2196,9 +2201,6 @@ static void info_change_lock(PAGECACHE_BLOCK_LINK *block, my_bool wl)
get_wrlock() get_wrlock()
pagecache pointer to a page cache data structure pagecache pointer to a page cache data structure
block the block to work with block the block to work with
user_file Unique handler per handler file. Used to check if
we request many write locks withing the same
statement
RETURN RETURN
0 - OK 0 - OK
...@@ -2206,11 +2208,15 @@ static void info_change_lock(PAGECACHE_BLOCK_LINK *block, my_bool wl) ...@@ -2206,11 +2208,15 @@ static void info_change_lock(PAGECACHE_BLOCK_LINK *block, my_bool wl)
*/ */
static my_bool get_wrlock(PAGECACHE *pagecache, static my_bool get_wrlock(PAGECACHE *pagecache,
PAGECACHE_BLOCK_LINK *block, PAGECACHE_BLOCK_LINK *block)
PAGECACHE_FILE *user_file)
{ {
PAGECACHE_FILE file= block->hash_link->file; PAGECACHE_FILE file= block->hash_link->file;
pgcache_page_no_t pageno= block->hash_link->pageno; pgcache_page_no_t pageno= block->hash_link->pageno;
#ifdef THREAD
pthread_t locker= pthread_self();
#else
int locker= 0;
#endif
DBUG_ENTER("get_wrlock"); DBUG_ENTER("get_wrlock");
DBUG_PRINT("info", ("the block 0x%lx " DBUG_PRINT("info", ("the block 0x%lx "
"files %d(%d) pages %lu(%lu)", "files %d(%d) pages %lu(%lu)",
...@@ -2218,7 +2224,7 @@ static my_bool get_wrlock(PAGECACHE *pagecache, ...@@ -2218,7 +2224,7 @@ static my_bool get_wrlock(PAGECACHE *pagecache,
file.file, block->hash_link->file.file, file.file, block->hash_link->file.file,
(ulong) pageno, (ulong) block->hash_link->pageno)); (ulong) pageno, (ulong) block->hash_link->pageno));
PCBLOCK_INFO(block); PCBLOCK_INFO(block);
while (block->wlocks && block->write_locker != user_file) while (block->wlocks && block->write_locker != locker)
{ {
/* Lock failed we will wait */ /* Lock failed we will wait */
#ifdef THREAD #ifdef THREAD
...@@ -2252,7 +2258,7 @@ static my_bool get_wrlock(PAGECACHE *pagecache, ...@@ -2252,7 +2258,7 @@ static my_bool get_wrlock(PAGECACHE *pagecache,
} }
/* we are doing it by global cache mutex protection, so it is OK */ /* we are doing it by global cache mutex protection, so it is OK */
block->wlocks++; block->wlocks++;
block->write_locker= user_file; block->write_locker= locker;
DBUG_PRINT("info", ("WR lock set, block 0x%lx", (ulong)block)); DBUG_PRINT("info", ("WR lock set, block 0x%lx", (ulong)block));
DBUG_RETURN(0); DBUG_RETURN(0);
} }
...@@ -2309,8 +2315,7 @@ static void release_wrlock(PAGECACHE_BLOCK_LINK *block) ...@@ -2309,8 +2315,7 @@ static void release_wrlock(PAGECACHE_BLOCK_LINK *block)
static my_bool make_lock_and_pin(PAGECACHE *pagecache, static my_bool make_lock_and_pin(PAGECACHE *pagecache,
PAGECACHE_BLOCK_LINK *block, PAGECACHE_BLOCK_LINK *block,
enum pagecache_page_lock lock, enum pagecache_page_lock lock,
enum pagecache_page_pin pin, enum pagecache_page_pin pin)
PAGECACHE_FILE *file)
{ {
DBUG_ENTER("make_lock_and_pin"); DBUG_ENTER("make_lock_and_pin");
...@@ -2331,7 +2336,7 @@ static my_bool make_lock_and_pin(PAGECACHE *pagecache, ...@@ -2331,7 +2336,7 @@ static my_bool make_lock_and_pin(PAGECACHE *pagecache,
switch (lock) { switch (lock) {
case PAGECACHE_LOCK_WRITE: /* free -> write */ case PAGECACHE_LOCK_WRITE: /* free -> write */
/* Writelock and pin the buffer */ /* Writelock and pin the buffer */
if (get_wrlock(pagecache, block, file)) if (get_wrlock(pagecache, block))
{ {
/* can't lock => need retry */ /* can't lock => need retry */
goto retry; goto retry;
...@@ -2638,7 +2643,7 @@ void pagecache_unlock(PAGECACHE *pagecache, ...@@ -2638,7 +2643,7 @@ void pagecache_unlock(PAGECACHE *pagecache,
(ulong) block)); (ulong) block));
} }
if (make_lock_and_pin(pagecache, block, lock, pin, file)) if (make_lock_and_pin(pagecache, block, lock, pin))
{ {
DBUG_ASSERT(0); /* should not happend */ DBUG_ASSERT(0); /* should not happend */
} }
...@@ -2708,7 +2713,7 @@ void pagecache_unpin(PAGECACHE *pagecache, ...@@ -2708,7 +2713,7 @@ void pagecache_unpin(PAGECACHE *pagecache,
*/ */
if (make_lock_and_pin(pagecache, block, if (make_lock_and_pin(pagecache, block,
PAGECACHE_LOCK_LEFT_READLOCKED, PAGECACHE_LOCK_LEFT_READLOCKED,
PAGECACHE_UNPIN, file)) PAGECACHE_UNPIN))
DBUG_ASSERT(0); /* should not happend */ DBUG_ASSERT(0); /* should not happend */
remove_reader(block); remove_reader(block);
...@@ -2767,8 +2772,7 @@ void pagecache_unlock_by_link(PAGECACHE *pagecache, ...@@ -2767,8 +2772,7 @@ void pagecache_unlock_by_link(PAGECACHE *pagecache,
if (pin == PAGECACHE_PIN_LEFT_UNPINNED && if (pin == PAGECACHE_PIN_LEFT_UNPINNED &&
lock == PAGECACHE_LOCK_READ_UNLOCK) lock == PAGECACHE_LOCK_READ_UNLOCK)
{ {
/* block do not need here so we do not provide it */ if (make_lock_and_pin(pagecache, block, lock, pin))
if (make_lock_and_pin(pagecache, 0, lock, pin, 0))
DBUG_ASSERT(0); /* should not happend */ DBUG_ASSERT(0); /* should not happend */
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
...@@ -2822,7 +2826,7 @@ void pagecache_unlock_by_link(PAGECACHE *pagecache, ...@@ -2822,7 +2826,7 @@ void pagecache_unlock_by_link(PAGECACHE *pagecache,
(ulong) block)); (ulong) block));
} }
if (make_lock_and_pin(pagecache, block, lock, pin, 0)) if (make_lock_and_pin(pagecache, block, lock, pin))
DBUG_ASSERT(0); /* should not happend */ DBUG_ASSERT(0); /* should not happend */
/* /*
...@@ -2885,7 +2889,7 @@ void pagecache_unpin_by_link(PAGECACHE *pagecache, ...@@ -2885,7 +2889,7 @@ void pagecache_unpin_by_link(PAGECACHE *pagecache,
*/ */
if (make_lock_and_pin(pagecache, block, if (make_lock_and_pin(pagecache, block,
PAGECACHE_LOCK_LEFT_READLOCKED, PAGECACHE_LOCK_LEFT_READLOCKED,
PAGECACHE_UNPIN, 0)) PAGECACHE_UNPIN))
DBUG_ASSERT(0); /* should not happend */ DBUG_ASSERT(0); /* should not happend */
/* /*
...@@ -3011,7 +3015,7 @@ restart: ...@@ -3011,7 +3015,7 @@ restart:
DBUG_PRINT("info", ("read is done")); DBUG_PRINT("info", ("read is done"));
} }
if (make_lock_and_pin(pagecache, block, lock, pin, file)) if (make_lock_and_pin(pagecache, block, lock, pin))
{ {
/* /*
We failed to write lock the block, cache is unlocked, We failed to write lock the block, cache is unlocked,
...@@ -3092,23 +3096,189 @@ no_key_cache: /* Key cache is not used */ ...@@ -3092,23 +3096,189 @@ no_key_cache: /* Key cache is not used */
/* /*
Delete page from the buffer @brief Delete page from the buffer (common part for link and file/page)
SYNOPSIS @param pagecache pointer to a page cache data structure
pagecache_delete() @param block direct link to page (returned by read or write)
pagecache pointer to a page cache data structure @param page_link hash link of the block
file handler for the file for the block of data to be read @param flush flush page if it is dirty
pageno number of the block of data in the file
lock lock change
flush flush page if it is dirty
RETURN VALUE @retval 0 deleted or was not present at all
0 - deleted or was not present at all @retval 1 error
1 - error
NOTES. */
lock can be only PAGECACHE_LOCK_LEFT_WRITELOCKED (page was write locked
before) or PAGECACHE_LOCK_WRITE (delete will write lock page before delete) static my_bool pagecache_delete_internal(PAGECACHE *pagecache,
PAGECACHE_BLOCK_LINK *block,
PAGECACHE_HASH_LINK *page_link,
my_bool flush)
{
int error= 0;
if (block->status & PCBLOCK_CHANGED)
{
if (flush)
{
/* The block contains a dirty page - push it out of the cache */
KEYCACHE_DBUG_PRINT("find_block", ("block is dirty"));
pagecache_pthread_mutex_unlock(&pagecache->cache_lock);
/*
The call is thread safe because only the current
thread might change the block->hash_link value
*/
DBUG_ASSERT(block->pins == 1);
error= pagecache_fwrite(pagecache,
&block->hash_link->file,
block->buffer,
block->hash_link->pageno,
block->type,
pagecache->readwrite_flags);
pagecache_pthread_mutex_lock(&pagecache->cache_lock);
pagecache->global_cache_write++;
if (error)
{
block->status|= PCBLOCK_ERROR;
my_debug_put_break_here();
goto err;
}
}
pagecache->blocks_changed--;
pagecache->global_blocks_changed--;
/*
free_block() will change the status and rec_lsn of the block so no
need to change them here.
*/
}
/* Cache is locked, so we can relese page before freeing it */
make_lock_and_pin(pagecache, block,
PAGECACHE_LOCK_WRITE_UNLOCK,
PAGECACHE_UNPIN);
DBUG_ASSERT(block->hash_link->requests > 0);
page_link->requests--;
/* See NOTE for pagecache_unlock about registering requests. */
free_block(pagecache, block);
err:
dec_counter_for_resize_op(pagecache);
return error;
}
/*
@brief Delete page from the buffer by link
@param pagecache pointer to a page cache data structure
@param link direct link to page (returned by read or write)
@param lock lock change
@param flush flush page if it is dirty
@retval 0 deleted or was not present at all
@retval 1 error
@note lock can be only PAGECACHE_LOCK_LEFT_WRITELOCKED (page was
write locked before) or PAGECACHE_LOCK_WRITE (delete will write
lock page before delete)
*/
my_bool pagecache_delete_by_link(PAGECACHE *pagecache,
PAGECACHE_BLOCK_LINK *block,
enum pagecache_page_lock lock,
my_bool flush)
{
int error= 0;
enum pagecache_page_pin pin= PAGECACHE_PIN_LEFT_PINNED;
DBUG_ENTER("pagecache_delete_by_link");
DBUG_PRINT("enter", ("fd: %d block 0x%lx %s %s",
block->hash_link->file.file,
(ulong) block,
page_cache_page_lock_str[lock],
page_cache_page_pin_str[pin]));
DBUG_ASSERT(lock == PAGECACHE_LOCK_WRITE ||
lock == PAGECACHE_LOCK_LEFT_WRITELOCKED);
DBUG_ASSERT(block->pins != 0); /* should be pinned */
restart:
if (pagecache->can_be_used)
{
pagecache_pthread_mutex_lock(&pagecache->cache_lock);
if (!pagecache->can_be_used)
goto end;
if (make_lock_and_pin(pagecache, block, lock, pin))
{
/*
We failed to writelock the block, cache is unlocked, and last write
lock is released, we will try to get the block again.
*/
pagecache_pthread_mutex_unlock(&pagecache->cache_lock);
DBUG_PRINT("info", ("restarting..."));
goto restart;
}
/*
get_present_hash_link() side effect emulation before call
pagecache_delete_internal()
*/
block->hash_link->requests++;
error= pagecache_delete_internal(pagecache, block, block->hash_link,
flush);
end:
pagecache_pthread_mutex_unlock(&pagecache->cache_lock);
}
DBUG_RETURN(error);
}
/**
@brief Returns "hits" for promotion
@return "hits" for promotion
*/
uint pagacache_pagelevel(PAGECACHE_BLOCK_LINK *block)
{
return block->hits_left;
}
/*
@brief Adds "hits" to the page
@param link direct link to page (returned by read or write)
@param level number of "hits" which we add to the page
*/
void pagecache_add_level_by_link(PAGECACHE_BLOCK_LINK *block,
uint level)
{
DBUG_ASSERT(block->pins != 0); /* should be pinned */
/*
Operation is just for statistics so it is not really important
if it interfere with other hit increasing => we are doing it without
locking the pagecache.
*/
block->hits_left+= level;
}
/*
@brief Delete page from the buffer
@param pagecache pointer to a page cache data structure
@param file handler for the file for the block of data to be read
@param pageno number of the block of data in the file
@param lock lock change
@param flush flush page if it is dirty
@retval 0 deleted or was not present at all
@retval 1 error
@note lock can be only PAGECACHE_LOCK_LEFT_WRITELOCKED (page was
write locked before) or PAGECACHE_LOCK_WRITE (delete will write
lock page before delete)
*/ */
my_bool pagecache_delete(PAGECACHE *pagecache, my_bool pagecache_delete(PAGECACHE *pagecache,
PAGECACHE_FILE *file, PAGECACHE_FILE *file,
...@@ -3152,7 +3322,7 @@ restart: ...@@ -3152,7 +3322,7 @@ restart:
if (pin == PAGECACHE_PIN) if (pin == PAGECACHE_PIN)
reg_requests(pagecache, block, 1); reg_requests(pagecache, block, 1);
DBUG_ASSERT(block != 0); DBUG_ASSERT(block != 0);
if (make_lock_and_pin(pagecache, block, lock, pin, file)) if (make_lock_and_pin(pagecache, block, lock, pin))
{ {
/* /*
We failed to writelock the block, cache is unlocked, and last write We failed to writelock the block, cache is unlocked, and last write
...@@ -3166,54 +3336,7 @@ restart: ...@@ -3166,54 +3336,7 @@ restart:
/* we can't delete with opened direct link for write */ /* we can't delete with opened direct link for write */
DBUG_ASSERT((block->status & PCBLOCK_DIRECT_W) == 0); DBUG_ASSERT((block->status & PCBLOCK_DIRECT_W) == 0);
if (block->status & PCBLOCK_CHANGED) error= pagecache_delete_internal(pagecache, block, page_link, flush);
{
if (flush)
{
/* The block contains a dirty page - push it out of the cache */
KEYCACHE_DBUG_PRINT("find_block", ("block is dirty"));
pagecache_pthread_mutex_unlock(&pagecache->cache_lock);
/*
The call is thread safe because only the current
thread might change the block->hash_link value
*/
DBUG_ASSERT(block->pins == 1);
error= pagecache_fwrite(pagecache,
&block->hash_link->file,
block->buffer,
block->hash_link->pageno,
block->type,
pagecache->readwrite_flags);
pagecache_pthread_mutex_lock(&pagecache->cache_lock);
pagecache->global_cache_write++;
if (error)
{
block->status|= PCBLOCK_ERROR;
my_debug_put_break_here();
goto err;
}
}
pagecache->blocks_changed--;
pagecache->global_blocks_changed--;
/*
free_block() will change the status and rec_lsn of the block so no
need to change them here.
*/
}
/* Cache is locked, so we can relese page before freeing it */
make_lock_and_pin(pagecache, block,
PAGECACHE_LOCK_WRITE_UNLOCK,
PAGECACHE_UNPIN, file);
DBUG_ASSERT(page_link->requests > 0);
page_link->requests--;
/* See NOTE for pagecache_unlock about registering requests. */
free_block(pagecache, block);
err:
dec_counter_for_resize_op(pagecache);
end: end:
pagecache_pthread_mutex_unlock(&pagecache->cache_lock); pagecache_pthread_mutex_unlock(&pagecache->cache_lock);
} }
...@@ -3409,7 +3532,7 @@ restart: ...@@ -3409,7 +3532,7 @@ restart:
write_lock_change_table[lock].new_lock, write_lock_change_table[lock].new_lock,
(need_lock_change ? (need_lock_change ?
write_pin_change_table[pin].new_pin : write_pin_change_table[pin].new_pin :
pin), file)) pin)))
{ {
/* /*
We failed to writelock the block, cache is unlocked, and last write We failed to writelock the block, cache is unlocked, and last write
...@@ -3493,7 +3616,7 @@ restart: ...@@ -3493,7 +3616,7 @@ restart:
*/ */
if (make_lock_and_pin(pagecache, block, if (make_lock_and_pin(pagecache, block,
write_lock_change_table[lock].unlock_lock, write_lock_change_table[lock].unlock_lock,
write_pin_change_table[pin].unlock_pin, file)) write_pin_change_table[pin].unlock_pin))
DBUG_ASSERT(0); DBUG_ASSERT(0);
} }
...@@ -3702,7 +3825,7 @@ static int flush_cached_blocks(PAGECACHE *pagecache, ...@@ -3702,7 +3825,7 @@ static int flush_cached_blocks(PAGECACHE *pagecache,
DBUG_ASSERT(block->wlocks == 0); DBUG_ASSERT(block->wlocks == 0);
DBUG_ASSERT(block->pins == 0); DBUG_ASSERT(block->pins == 0);
if (make_lock_and_pin(pagecache, block, if (make_lock_and_pin(pagecache, block,
PAGECACHE_LOCK_WRITE, PAGECACHE_PIN, 0)) PAGECACHE_LOCK_WRITE, PAGECACHE_PIN))
DBUG_ASSERT(0); DBUG_ASSERT(0);
KEYCACHE_DBUG_PRINT("flush_cached_blocks", KEYCACHE_DBUG_PRINT("flush_cached_blocks",
...@@ -3735,7 +3858,7 @@ static int flush_cached_blocks(PAGECACHE *pagecache, ...@@ -3735,7 +3858,7 @@ static int flush_cached_blocks(PAGECACHE *pagecache,
make_lock_and_pin(pagecache, block, make_lock_and_pin(pagecache, block,
PAGECACHE_LOCK_WRITE_UNLOCK, PAGECACHE_LOCK_WRITE_UNLOCK,
PAGECACHE_UNPIN, 0); PAGECACHE_UNPIN);
pagecache->global_cache_write++; pagecache->global_cache_write++;
if (error) if (error)
......
...@@ -283,6 +283,10 @@ extern my_bool pagecache_delete(PAGECACHE *pagecache, ...@@ -283,6 +283,10 @@ extern my_bool pagecache_delete(PAGECACHE *pagecache,
pgcache_page_no_t pageno, pgcache_page_no_t pageno,
enum pagecache_page_lock lock, enum pagecache_page_lock lock,
my_bool flush); my_bool flush);
extern my_bool pagecache_delete_by_link(PAGECACHE *pagecache,
PAGECACHE_BLOCK_LINK *link,
enum pagecache_page_lock lock,
my_bool flush);
extern my_bool pagecache_delete_pages(PAGECACHE *pagecache, extern my_bool pagecache_delete_pages(PAGECACHE *pagecache,
PAGECACHE_FILE *file, PAGECACHE_FILE *file,
pgcache_page_no_t pageno, pgcache_page_no_t pageno,
...@@ -296,6 +300,9 @@ extern my_bool pagecache_collect_changed_blocks_with_lsn(PAGECACHE *pagecache, ...@@ -296,6 +300,9 @@ extern my_bool pagecache_collect_changed_blocks_with_lsn(PAGECACHE *pagecache,
extern int reset_pagecache_counters(const char *name, PAGECACHE *pagecache); extern int reset_pagecache_counters(const char *name, PAGECACHE *pagecache);
extern uchar *pagecache_block_link_to_buffer(PAGECACHE_BLOCK_LINK *block); extern uchar *pagecache_block_link_to_buffer(PAGECACHE_BLOCK_LINK *block);
extern uint pagacache_pagelevel(PAGECACHE_BLOCK_LINK *block);
extern void pagecache_add_level_by_link(PAGECACHE_BLOCK_LINK *block,
uint level);
/* Functions to handle multiple key caches */ /* Functions to handle multiple key caches */
extern my_bool multi_pagecache_init(void); extern my_bool multi_pagecache_init(void);
......
...@@ -453,6 +453,7 @@ int simple_delete_flush_test() ...@@ -453,6 +453,7 @@ int simple_delete_flush_test()
{ {
unsigned char *buffw= malloc(PAGE_SIZE); unsigned char *buffw= malloc(PAGE_SIZE);
unsigned char *buffr= malloc(PAGE_SIZE); unsigned char *buffr= malloc(PAGE_SIZE);
PAGECACHE_BLOCK_LINK *link;
int res; int res;
DBUG_ENTER("simple_delete_flush_test"); DBUG_ENTER("simple_delete_flush_test");
/* prepare the file */ /* prepare the file */
...@@ -462,7 +463,7 @@ int simple_delete_flush_test() ...@@ -462,7 +463,7 @@ int simple_delete_flush_test()
PAGECACHE_LOCK_WRITE, PAGECACHE_LOCK_WRITE,
PAGECACHE_PIN, PAGECACHE_PIN,
PAGECACHE_WRITE_DELAY, PAGECACHE_WRITE_DELAY,
0, LSN_IMPOSSIBLE); &link, LSN_IMPOSSIBLE);
flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE);
/* test */ /* test */
bfill(buffw, PAGE_SIZE, '\2'); bfill(buffw, PAGE_SIZE, '\2');
...@@ -472,12 +473,16 @@ int simple_delete_flush_test() ...@@ -472,12 +473,16 @@ int simple_delete_flush_test()
PAGECACHE_PIN_LEFT_PINNED, PAGECACHE_PIN_LEFT_PINNED,
PAGECACHE_WRITE_DELAY, PAGECACHE_WRITE_DELAY,
0, LSN_IMPOSSIBLE); 0, LSN_IMPOSSIBLE);
pagecache_delete(&pagecache, &file1, 0, if (pagecache_delete_by_link(&pagecache, link,
PAGECACHE_LOCK_LEFT_WRITELOCKED, 1); PAGECACHE_LOCK_LEFT_WRITELOCKED, 1))
{
diag("simple_delete_flush_test: error during delete");
exit(1);
}
flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE); flush_pagecache_blocks(&pagecache, &file1, FLUSH_FORCE_WRITE);
ok((res= test(test_file(file1, file1_name, PAGE_SIZE, PAGE_SIZE, ok((res= test(test_file(file1, file1_name, PAGE_SIZE, PAGE_SIZE,
simple_delete_flush_test_file))), simple_delete_flush_test_file))),
"Simple delete-forget page file"); "Simple delete flush (link) page file");
if (res) if (res)
reset_file(&file1, file1_name); reset_file(&file1, file1_name);
free(buffw); free(buffw);
......
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