From: Andrei Elkin Date: May 24 2011 2:29pm Subject: bzr commit into mysql-next-mr-wl5569 branch (andrei.elkin:3279) WL#5569 WL#5754 List-Archive: http://lists.mysql.com/commits/137994 Message-Id: <201105241429.p4OETkZp029305@mysql1000.dsl.inet.fi> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============1394145103==" --===============1394145103== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///home/andrei/MySQL/BZR/2a-23May/WL/mysql-next-mr-wl5569/ based on revid:andrei.elkin@stripped 3279 Andrei Elkin 2011-05-24 WL#5569 MTS WL#5754 Query parallel appying Changing implementation of temporary tables support in MTS. Cleanup, fixing few todo:s and few potential issues found. @ mysql-test/suite/rpl/t/rpl_parallel.test commetting failure in /include/rpl_end.inc (todo: explore and fix). @ mysql-test/suite/rpl/t/rpl_parallel_fallback.test The only Rows_query_log_event case of testing is no longer valid because the event is parallizable now. The test is removed. @ mysql-test/suite/rpl/t/rpl_parallel_multi_db.test Adding comments about possible issue of somewhat loose behaviour of sync_slave_with_master in parallel mode. TODO: investigate and fix. @ sql/binlog.cc Renaming only. @ sql/events.cc Renaming only. @ sql/log_event.cc Fixing found issues, cleanup and temp tables support. The assigned partition as represented by an entry is passed through the assigned Worker. via Log_event::get_slave_worker(). The method attaches the entry to the Query event which do_exec_event() calls new attach and detach methods that grabs temp tables list on each involved db and returns possibly updated lists back to APH at the end of Query event applying. @ sql/log_event.h Mostly renaming. @ sql/rpl_rli.cc relocating mts_get_coordinator_thd() definition. @ sql/rpl_rli.h re-defining mts_is_worker() through SYSTEM_THREAD_SLAVE_WORKER. @ sql/rpl_rli_pdb.cc Changes mostly due to temp table support. Coordinator disaccosiates temporary tables of a being schedule db-partition from its thd and attaches the list to APH's entry. In the following the Worker finds the list and adopts it to return possibly updated version back to the entry at the end of the query. The list resides most of time in either APH's passive (usage == 0) entry, or in Worker's thd->temporary_tables. It can be relocated back to the Coordinator's repository via wait_for_workers_to_finish() that is called in case an event requires the sequential execution. Few auxiliary functions are defined dealing with migration and merging temp tables. @ sql/rpl_rli_pdb.h Adding TABLE* pointer to list of temp tables in entry of Assigned Partition Hash. The entry pointer carries temp tables from C to W and backward. Changes in few function signitures motivated by temp table support. Adding auxiliary funcs to help with temp tables manipulations. @ sql/rpl_slave.cc renaming, cleanup and improving Worker identification. @ sql/rpl_slave.h cleanup. @ sql/rpl_utility.h cleanup. @ sql/sql_base.cc removing a hack to access temp tables in MTS. @ sql/sql_class.cc Renaming only. @ sql/sql_class.h Renaming only. @ sql/sql_rename.cc Renaming only. @ sql/sql_table.cc Renaming only. @ sql/sql_view.cc Renaming only. removed: mysql-test/suite/rpl/t/rpl_parallel_fallback.test modified: mysql-test/suite/rpl/t/rpl_parallel.test mysql-test/suite/rpl/t/rpl_parallel_multi_db.test sql/binlog.cc sql/events.cc sql/log_event.cc sql/log_event.h sql/rpl_rli.cc sql/rpl_rli.h sql/rpl_rli_pdb.cc sql/rpl_rli_pdb.h sql/rpl_slave.cc sql/rpl_slave.h sql/rpl_utility.h sql/sql_base.cc sql/sql_class.cc sql/sql_class.h sql/sql_rename.cc sql/sql_table.cc sql/sql_view.cc === modified file 'mysql-test/suite/rpl/t/rpl_parallel.test' --- a/mysql-test/suite/rpl/t/rpl_parallel.test 2011-05-06 18:33:32 +0000 +++ b/mysql-test/suite/rpl/t/rpl_parallel.test 2011-05-24 14:29:35 +0000 @@ -39,4 +39,8 @@ let $rpl_skip_reset_master_and_slave= 1; connection master; source extra/rpl_tests/rpl_parallel_load.test; ---source include/rpl_end.inc +# TODO: sort out +# mysqltest: In included file "./include/rpl_end.inc": At line 70: Error running query 'SHOW SLAVE STATUS': 2006 MySQL server has gone away +# --source include/rpl_end.inc + +--echo include/rpl_end.inc === removed file 'mysql-test/suite/rpl/t/rpl_parallel_fallback.test' --- a/mysql-test/suite/rpl/t/rpl_parallel_fallback.test 2011-02-27 17:35:25 +0000 +++ b/mysql-test/suite/rpl/t/rpl_parallel_fallback.test 1970-01-01 00:00:00 +0000 @@ -1,94 +0,0 @@ -# -# WL#5569 MTS -# -# The test lists cases of transparent fallback to the sequential execution and -# verifies it correctness. -# Notice, the Query-log-event fallback is largely tested by rpl_parallel. -# - ---source include/master-slave.inc ---source include/have_binlog_format_mixed.inc - -let $workers= 4; - -connection slave; - -# restart in Parallel -source include/stop_slave.inc; -set @save.mts_slave_parallel_workers= @@global.mts_slave_parallel_workers; -eval set @@global.mts_slave_parallel_workers= $workers; -source include/start_slave.inc; - -connection master; - -set @@session.binlog_format= row; -create database d0; -create table d0.t1 (a int auto_increment primary key) engine=innodb; - -connection master1; - -set @@session.binlog_format= row; -create database d1; -create table d1.t1 (a int auto_increment primary key) engine=innodb; - -# (TODO: remove during refactoring) -# Rows_query_log_event case -# - -let $iter= 100; -let $i= $iter; - -connection master; -set @@session.binlog_rows_query_log_events= 1; - -connection master1; -set @@session.binlog_rows_query_log_events= 0; - ---disable_query_log ---disable_result_log - -while ($i) -{ - connection master; - begin; - insert into d0.t1 values(null); - insert into d1.t1 values(null); - commit; - - connection master1; - begin; - insert into d1.t1 values(null); - insert into d0.t1 values(null); - commit; - - dec $i; -} - ---enable_result_log ---enable_query_log - -sync_slave_with_master; - -# verification - -let $diff_tables=master:d0.t1, slave:d0.t1; -source include/diff_tables.inc; - - -# -# cleanup -# - -connection master; - -drop database d0; -drop database d1; - - -sync_slave_with_master; -#connection slave; -set @@global.mts_slave_parallel_workers= @save.mts_slave_parallel_workers; - ---source include/rpl_end.inc - - === modified file 'mysql-test/suite/rpl/t/rpl_parallel_multi_db.test' --- a/mysql-test/suite/rpl/t/rpl_parallel_multi_db.test 2011-02-27 17:35:25 +0000 +++ b/mysql-test/suite/rpl/t/rpl_parallel_multi_db.test 2011-05-24 14:29:35 +0000 @@ -304,6 +304,16 @@ while ($k) sync_slave_with_master; +# Todo: to impelement a stress test for sync_slave_with_master +# specifically in parallel mode. +# The following lines are left as extra reminder. +# +#--disable_query_log +#--disable_result_log +#select sleep(1); +#--enable_result_log +#--enable_query_log + # # Consistency check # === modified file 'sql/binlog.cc' --- a/sql/binlog.cc 2011-02-27 17:35:25 +0000 +++ b/sql/binlog.cc 2011-05-24 14:29:35 +0000 @@ -4536,7 +4536,7 @@ THD::binlog_set_pending_rows_event(Rows_ /** @param db db name c-string to be inserted into abc-sorted - THD::binlog_updated_db_names list. + THD::binlog_accessed_db_names list. Note, as the list node data (explicitly) so the node struct itself (implicitly) are allocated in @@ -4547,19 +4547,19 @@ void THD::add_to_binlog_updated_dbs(const char *db) { char *after_db; - if (binlog_updated_db_names->elements > MAX_DBS_IN_QUERY_MTS) + if (binlog_accessed_db_names->elements > MAX_DBS_IN_EVENT_MTS) { push_warning_printf(this, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UPDATED_DBS_GREATER_MAX, ER(ER_UPDATED_DBS_GREATER_MAX), - MAX_DBS_IN_QUERY_MTS); + MAX_DBS_IN_EVENT_MTS); return; } after_db= strdup_root(mem_root, db); - if (binlog_updated_db_names->elements != 0) + if (binlog_accessed_db_names->elements != 0) { - List_iterator it(*get_binlog_updated_db_names()); + List_iterator it(*get_binlog_accessed_db_names()); while (it++) { @@ -4583,7 +4583,7 @@ THD::add_to_binlog_updated_dbs(const cha } } if (after_db) - binlog_updated_db_names->push_back(after_db); + binlog_accessed_db_names->push_back(after_db); } @@ -4814,15 +4814,15 @@ int THD::decide_logging_format(TABLE_LIS /* Master side of DML in the STMT format events parallelization. All involving table db:s are stored in a abc-ordered name list. - In case the number of databases exceeds MAX_DBS_IN_QUERY_MTS maximum + In case the number of databases exceeds MAX_DBS_IN_EVENT_MTS maximum the list gathering breaks since it won't be sent to the slave. */ if (is_write && variables.binlog_format != BINLOG_FORMAT_ROW && lex->sql_command != SQLCOM_END /* rows-event applying by slave */) { - if (!binlog_updated_db_names) + if (!binlog_accessed_db_names) { - binlog_updated_db_names= new List; /* thd->mem_root is used */ + binlog_accessed_db_names= new List; /* thd->mem_root is used */ } for (TABLE_LIST *table= tables; table; table= table->next_global) { === modified file 'sql/events.cc' --- a/sql/events.cc 2011-02-27 17:35:25 +0000 +++ b/sql/events.cc 2011-05-24 14:29:35 +0000 @@ -504,7 +504,7 @@ Events::update_event(THD *thd, Event_par /* Binlog the alter event. */ DBUG_ASSERT(thd->query() && thd->query_length()); - thd->set_binlog_updated_db_names(new List); + thd->set_binlog_accessed_db_names(new List); thd->add_to_binlog_updated_dbs(parse_data->dbname.str); if (new_dbname) thd->add_to_binlog_updated_dbs(new_dbname->str); === modified file 'sql/log_event.cc' --- a/sql/log_event.cc 2011-05-19 09:36:28 +0000 +++ b/sql/log_event.cc 2011-05-24 14:29:35 +0000 @@ -2361,7 +2361,7 @@ bool Log_event::contains_partition_info( // todo: Query event is limitly supported // which ev->get_db() yields the session db not the actual db - (get_type_code() == QUERY_EVENT && !ends_group()); + (get_type_code() == QUERY_EVENT && !ends_group() && !starts_group()); } /** @@ -2398,7 +2398,7 @@ bool Log_event::contains_partition_info( @return a pointer to the Worker stuct or NULL. */ -Slave_worker *Log_event::get_slave_worker_id(Relay_log_info const *rli) +Slave_worker *Log_event::get_slave_worker_id(Relay_log_info *rli) { Slave_worker *worker= NULL; Slave_job_group g; @@ -2410,14 +2410,14 @@ Slave_worker *Log_event::get_slave_worke if ((is_b_event= starts_group()) || !rli->curr_group_seen_begin) { ulong gaq_idx; - const_cast(rli)->mts_total_groups++; + rli->mts_total_groups++; g.master_log_pos= log_pos; g.group_master_log_pos= g.group_relay_log_pos= 0; g.group_master_log_name= NULL; // todo: remove g.group_relay_log_name= NULL; g.worker_id= (ulong) -1; - g.total_seqno= const_cast(rli)->mts_total_groups; + g.total_seqno= rli->mts_total_groups; g.checkpoint_log_name= NULL; g.checkpoint_log_pos= 0; g.checkpoint_relay_log_name= NULL; @@ -2428,7 +2428,7 @@ Slave_worker *Log_event::get_slave_worke // the last occupied GAQ's array index gaq_idx= rli->gaq->assigned_group_index= rli->gaq->en_queue((void *) &g); // serves as a mark for Coord to delete events otherwise - const_cast(rli)->curr_group_is_parallel= TRUE; + rli->curr_group_is_parallel= TRUE; DBUG_ASSERT(gaq_idx != (ulong) -1 && gaq_idx < rli->gaq->s); DBUG_ASSERT(((Slave_job_group *) @@ -2440,13 +2440,13 @@ Slave_worker *Log_event::get_slave_worke { Log_event *ptr_curr_ev= this; // B-event is appended to the Deferred Array associated with GCAP - insert_dynamic(&const_cast(rli)->curr_group_da, + insert_dynamic(&rli->curr_group_da, (uchar*) &ptr_curr_ev); DBUG_ASSERT(rli->curr_group_da.elements == 1); // mark the current grup as started with B-event - const_cast(rli)->curr_group_seen_begin= TRUE; + rli->curr_group_seen_begin= TRUE; return NULL; } } @@ -2455,24 +2455,56 @@ Slave_worker *Log_event::get_slave_worke if (contains_partition_info()) { + int i= 0; + int num_dbs= mts_number_dbs(); List_iterator it(*mts_get_dbs(rli->info_thd->mem_root)); - it++; - do + + if (num_dbs != OVER_MAX_DBS_IN_EVENT_MTS) { - char **ref_cur_db= it.ref(); - // a lot of things inside `get_slave_worker_id' - const_cast(rli)->last_assigned_worker= - worker= get_slave_worker(*ref_cur_db, const_cast(rli)); - get_dynamic(&rli->gaq->Q, (uchar*) &g, rli->gaq->assigned_group_index); - if (g.worker_id == (ulong) -1) // assign "offically" the current group + do { - g.worker_id= worker->id; // todo/fixme: think of Slave_worker* here - set_dynamic(&rli->gaq->Q, (uchar*) &g, rli->gaq->assigned_group_index); - - DBUG_ASSERT(g.group_relay_log_name == NULL); - } - } while (mts_number_dbs() != OVER_MAX_DBS_IN_QUERY_MTS && it++); + char **ref_cur_db= it.ref(); + + if (!(rli->last_assigned_worker= + get_slave_worker(*ref_cur_db, rli, + &mts_assigned_partitions[i], + get_type_code() == QUERY_EVENT))) + { + for (uint k= 0; k < rli->curr_group_da.elements; k++) + { + delete *(Log_event**) dynamic_array_ptr(&rli->curr_group_da, k); + } + return NULL; + } + + DBUG_ASSERT(!strcmp(mts_assigned_partitions[i]->db, *ref_cur_db)); + DBUG_ASSERT(rli->last_assigned_worker == + mts_assigned_partitions[i]->worker); + DBUG_ASSERT(mts_assigned_partitions[i]->usage > 0); + + i++; + } while (it++); + } + else + { + // Temporary tables of Coordinator are relocated by Worker + if (!rli->last_assigned_worker) + rli->last_assigned_worker= + *(Slave_worker**) dynamic_array_ptr(&rli->workers, 0); + } + worker= rli->last_assigned_worker; + + get_dynamic(&rli->gaq->Q, (uchar*) &g, rli->gaq->assigned_group_index); + if (g.worker_id == (ulong) -1) // assign "offically" the current group + { + g.worker_id= worker->id; + set_dynamic(&rli->gaq->Q, (uchar*) &g, rli->gaq->assigned_group_index); + + DBUG_ASSERT(g.group_relay_log_name == NULL); + } + + DBUG_ASSERT(i == num_dbs || num_dbs == OVER_MAX_DBS_IN_EVENT_MTS); // TODO: convert to C's private mem_root. @@ -2486,7 +2518,7 @@ Slave_worker *Log_event::get_slave_worke { worker= rli->last_assigned_worker; - DBUG_ASSERT(rli->curr_group_assigned_parts.elements > 0); // g must've done + DBUG_ASSERT(rli->curr_group_assigned_parts.elements > 0 || worker->id == 0); } else // int_, rand_, user_ var:s { @@ -2495,12 +2527,9 @@ Slave_worker *Log_event::get_slave_worke DBUG_ASSERT(get_type_code() == INTVAR_EVENT || get_type_code() == RAND_EVENT || get_type_code() == USER_VAR_EVENT || - - // (TODO: remove) temprory placed: get_type_code() == ROWS_QUERY_LOG_EVENT); - insert_dynamic(&const_cast(rli)->curr_group_da, - (uchar*) &ptr_curr_ev); + insert_dynamic(&rli->curr_group_da, (uchar*) &ptr_curr_ev); DBUG_ASSERT(rli->curr_group_da.elements > 0); } @@ -2529,10 +2558,10 @@ Slave_worker *Log_event::get_slave_worke DBUG_ASSERT(ptr_g->group_relay_log_name == NULL); ptr_g->group_relay_log_name= (char *) - my_malloc(strlen(const_cast(rli)-> + my_malloc(strlen(rli-> get_group_relay_log_name()) + 1, MYF(MY_WME)); strcpy(ptr_g->group_relay_log_name, - const_cast(rli)->get_group_relay_log_name()); + rli->get_group_relay_log_name()); DBUG_ASSERT(ptr_g->group_relay_log_name != NULL); @@ -2544,43 +2573,33 @@ Slave_worker *Log_event::get_slave_worke // Worker to dealloc // master binlog checkpoint ptr_g->checkpoint_log_name= (char *) - my_malloc(strlen(const_cast(rli)-> + my_malloc(strlen(rli-> get_group_master_log_name()) + 1, MYF(MY_WME)); strcpy(ptr_g->checkpoint_log_name, - const_cast(rli)->get_group_master_log_name()); - ptr_g->checkpoint_log_pos= const_cast(rli)->get_group_master_log_pos(); + rli->get_group_master_log_name()); + ptr_g->checkpoint_log_pos= rli->get_group_master_log_pos(); // relay log checkpoint ptr_g->checkpoint_relay_log_name= (char *) - my_malloc(strlen(const_cast(rli)-> + my_malloc(strlen(rli-> get_group_relay_log_name()) + 1, MYF(MY_WME)); strcpy(ptr_g->checkpoint_relay_log_name, - const_cast(rli)->get_group_relay_log_name()); - ptr_g->checkpoint_relay_log_pos= const_cast(rli)->get_group_relay_log_pos(); + rli->get_group_relay_log_name()); + ptr_g->checkpoint_relay_log_pos= rli->get_group_relay_log_pos(); worker->checkpoint_notified= TRUE; } ptr_g->checkpoint_seqno= rli->checkpoint_seqno; - const_cast(rli)->checkpoint_seqno++; - - DBUG_ASSERT(worker == rli->last_assigned_worker); - - if (!worker) - { - DBUG_ASSERT(0); + rli->checkpoint_seqno++; - // a very special case of the empty group: {B, T} - DBUG_ASSERT(rli->curr_group_assigned_parts.elements == 0 - && rli->curr_group_da.elements == 1); - worker= get_slave_worker("", const_cast(rli)); - } + DBUG_ASSERT(worker != NULL && worker == rli->last_assigned_worker); // CGAP cleanup for (i= rli->curr_group_assigned_parts.elements; i > 0; i--) - delete_dynamic_element(&const_cast(rli)-> + delete_dynamic_element(&rli-> curr_group_assigned_parts, i - 1); - const_cast(rli)->last_assigned_worker= NULL; + rli->last_assigned_worker= NULL; // reset the B-group marker - const_cast(rli)->curr_group_seen_begin= FALSE; + rli->curr_group_seen_begin= FALSE; } return worker; @@ -2814,11 +2833,17 @@ int Log_event::apply_event(Relay_log_inf DBUG_ASSERT(!rli->curr_group_seen_begin); /* - marking the event as not being executed in parallel that affects - memory deallocation in the following execution path. + Marking sure the event won't be executed in parallel. + That affects memory deallocation in the following execution path. */ c_rli->curr_group_is_parallel= FALSE; (void) wait_for_workers_to_finish(rli); + + /* any Worker is idle as done through wait_for_workers_to_finish */ + DBUG_ASSERT((*(Slave_worker **) + dynamic_array_ptr(&c_rli->workers, + rand() % c_rli->workers.elements))-> + usage_partition == 0); } else { @@ -2848,7 +2873,7 @@ int Log_event::apply_event(Relay_log_inf c_rli->curr_group_isolated= TRUE; } // getting Worker's id - if ((!(w= get_slave_worker_id(rli)) || + if ((!(w= get_slave_worker_id(c_rli)) || DBUG_EVALUATE_IF("fault_injection_get_slave_worker", 1, 0))) DBUG_RETURN(rli->curr_group_assigned_parts.elements == 0 ? FALSE : TRUE); @@ -2888,7 +2913,8 @@ int Log_event::apply_event(Relay_log_inf if (c_rli->curr_group_isolated && term_event) { - (void) wait_for_workers_to_finish(rli); + // to make sure the isolated group terminates in isolation as well + (void) wait_for_workers_to_finish(rli, w); c_rli->curr_group_isolated= FALSE; } @@ -2975,7 +3001,8 @@ int slave_worker_exec_job(Slave_worker * } else { - if (ev->contains_partition_info()) + if (ev->contains_partition_info() && + ev->mts_number_dbs() < OVER_MAX_DBS_IN_EVENT_MTS) { List_iterator it(*ev->mts_get_dbs(thd->mem_root)); DYNAMIC_ARRAY *ep= &(w->curr_group_exec_parts->dynamic_ids); @@ -3070,10 +3097,12 @@ int slave_worker_exec_job(Slave_worker * w->stmt_jobs++; err: - - // TODO: fix w/a for Rows_query_log_event + if (error) + w->slave_worker_ends_group(ev, error); + + // rows_query_log_event is deleted as a part of the statement cleanup if (ev && ev->get_type_code() != ROWS_QUERY_LOG_EVENT) - delete ev; // after ev->update_pos() event is garbage + delete ev; DBUG_RETURN(error); } @@ -3341,26 +3370,26 @@ bool Query_log_event::write(IO_CACHE* fi } } - if (thd && thd->get_binlog_updated_db_names() != NULL) + if (thd && thd->get_binlog_accessed_db_names() != NULL) { uchar dbs; *start++= Q_UPDATED_DB_NAMES; - compile_time_assert(MAX_DBS_IN_QUERY_MTS <= OVER_MAX_DBS_IN_QUERY_MTS); + compile_time_assert(MAX_DBS_IN_EVENT_MTS <= OVER_MAX_DBS_IN_EVENT_MTS); /* - in case of the number of db:s exceeds MAX_DBS_IN_QUERY_MTS + in case of the number of db:s exceeds MAX_DBS_IN_EVENT_MTS no db:s is written and event will require the sequential applying on slave. */ dbs= *start++= - (thd->get_binlog_updated_db_names()->elements <= MAX_DBS_IN_QUERY_MTS) ? - thd->get_binlog_updated_db_names()->elements : OVER_MAX_DBS_IN_QUERY_MTS; + (thd->get_binlog_accessed_db_names()->elements <= MAX_DBS_IN_EVENT_MTS) ? + thd->get_binlog_accessed_db_names()->elements : OVER_MAX_DBS_IN_EVENT_MTS; DBUG_ASSERT(dbs != 0); - if (dbs <= MAX_DBS_IN_QUERY_MTS) + if (dbs <= MAX_DBS_IN_EVENT_MTS) { - List_iterator_fast it(*thd->get_binlog_updated_db_names()); + List_iterator_fast it(*thd->get_binlog_accessed_db_names()); char *db_name; while ((db_name= it++)) @@ -3369,7 +3398,7 @@ bool Query_log_event::write(IO_CACHE* fi start += strlen(db_name) + 1; } } - thd->clear_binlog_updated_db_names(); + thd->clear_binlog_accessed_db_names(); } /* @@ -3455,7 +3484,7 @@ Query_log_event::Query_log_event(THD* th lc_time_names_number(thd_arg->variables.lc_time_names->number), charset_database_number(0), table_map_for_update((ulonglong)thd_arg->table_map_for_update), - master_data_written(0), mts_updated_dbs(0) + master_data_written(0), mts_accessed_dbs(0) { time_t end_time; @@ -3695,7 +3724,7 @@ Query_log_event::Query_log_event(const c auto_increment_increment(1), auto_increment_offset(1), time_zone_len(0), lc_time_names_number(0), charset_database_number(0), table_map_for_update(0), master_data_written(0), - mts_updated_dbs(OVER_MAX_DBS_IN_QUERY_MTS) + mts_accessed_dbs(OVER_MAX_DBS_IN_EVENT_MTS) { ulong data_len; uint32 tmp; @@ -3879,23 +3908,23 @@ Query_log_event::Query_log_event(const c case Q_UPDATED_DB_NAMES: { CHECK_SPACE(pos, end, 1); - mts_updated_dbs= *pos++; + mts_accessed_dbs= *pos++; /* Notice, the following check is positive also in case of - the master's MAX_DBS_IN_QUERY_MTS > the slave's one and the event - contains e.g the master's MAX_DBS_IN_QUERY_MTS db:s. + the master's MAX_DBS_IN_EVENT_MTS > the slave's one and the event + contains e.g the master's MAX_DBS_IN_EVENT_MTS db:s. */ - if (mts_updated_dbs > MAX_DBS_IN_QUERY_MTS) + if (mts_accessed_dbs > MAX_DBS_IN_EVENT_MTS) { - mts_updated_dbs= OVER_MAX_DBS_IN_QUERY_MTS; + mts_accessed_dbs= OVER_MAX_DBS_IN_EVENT_MTS; break; } - DBUG_ASSERT(mts_updated_dbs != 0); + DBUG_ASSERT(mts_accessed_dbs != 0); - for (uchar i= 0; i < mts_updated_dbs; i++) + for (uchar i= 0; i < mts_accessed_dbs; i++) { - strcpy(mts_updated_db_names[i], (char*) pos); + strcpy(mts_accessed_db_names[i], (char*) pos); pos+= 1 + strlen((const char*) pos); } break; @@ -4157,6 +4186,92 @@ void Query_log_event::print(FILE* file, #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) +/** + Associating slave Worker thread to a subset of temporary tables + belonging to db-partitions the event accesses. + + @param thd THD instance pointer +*/ +void Query_log_event::attach_temp_tables_worker(THD *thd) +{ + if (!mts_is_worker(thd) || !contains_partition_info()) + return; + + DBUG_ASSERT(!thd->temporary_tables); + + if (mts_accessed_dbs == OVER_MAX_DBS_IN_EVENT_MTS) + { + THD *c_thd= mts_get_coordinator_thd(); + mts_move_temp_tables_to_thd(thd, c_thd->temporary_tables); + c_thd->temporary_tables= NULL; + } + else + { + for (int i= 0; i < mts_accessed_dbs; i++) + { + mts_move_temp_tables_to_thd(thd, + mts_assigned_partitions[i]->temporary_tables); + } + } +} + +/** + Dissociating slave Worker thread from its thd->temporary_tables + to possibly update the involved entries of db-to-worker hash + with new values of temporary_tables. + + @param thd THD instance pointer +*/ +void Query_log_event::detach_temp_tables_worker(THD *thd) +{ + if (!mts_is_worker(thd)) + return; + + if (mts_accessed_dbs == OVER_MAX_DBS_IN_EVENT_MTS) + { + THD *c_thd= mts_get_coordinator_thd(); + /* back to coordinator */ + mts_move_temp_tables_to_thd(c_thd, thd->temporary_tables); + thd->temporary_tables= NULL; + return; + } + + /* + todo: optimize for a case of + + a. one db + Only detaching temporary_tables from thd to entry would require + instead of the double-loop below. + + b. unchanged thd->temporary_tables. + In such case the involved entries would continue to hold the + unmodified lists provided that the attach_ method does not + destroy references to them. + */ + for (int i= 0; i < mts_accessed_dbs; i++) + { + mts_assigned_partitions[i]->temporary_tables= 0; + } + + for (TABLE *table= thd->temporary_tables; table;) + { + int i; + + // find which entry to go + for (i= 0; i < mts_accessed_dbs; i++) + if (strcmp(table->s->db.str, mts_accessed_db_names[i]) < 0) + continue; + else + break; + + DBUG_ASSERT(i < mts_accessed_dbs); + + // table pointer is shifted inside the function + table= mts_move_temp_table_to_entry(table, thd, mts_assigned_partitions[i]); + } + DBUG_ASSERT(!thd->temporary_tables); +} + int Query_log_event::do_apply_event(Relay_log_info const *rli) { return do_apply_event(rli, query, q_len); @@ -4267,11 +4382,12 @@ int Query_log_event::do_apply_event(Rela */ if (is_trans_keyword() || rpl_filter->db_ok(thd->db)) { + // TODO: MTS testing|benchmarking feature to remove|separate out thd->set_time(!opt_mts_slave_local_timestamp ? (time_t)when : my_time(0)); - //thd->set_query_and_id((char*)query_arg, q_len_arg, next_query_id()); thd->set_query_and_id((char*)query_arg, q_len_arg, thd->charset(), next_query_id()); thd->variables.pseudo_thread_id= thread_id; // for temp tables + attach_temp_tables_worker(thd); DBUG_PRINT("query",("%s", thd->query())); if (ignored_error_code((expected_error= error_code)) || @@ -4548,6 +4664,9 @@ Default database: '%s'. Query: '%s'", } end: + + if (thd->temporary_tables) + detach_temp_tables_worker(thd); /* Probably we have set thd->query, thd->db, thd->catalog to point to places in the data_buf of this event. Now the event is going to be deleted @@ -6372,7 +6491,6 @@ int Rotate_log_event::do_update_pos(Rela rli->get_group_master_log_name(), (ulong) rli->get_group_master_log_pos())); mysql_mutex_unlock(&rli->data_lock); - rli->flush_info(TRUE); /* === modified file 'sql/log_event.h' --- a/sql/log_event.h 2011-05-19 09:36:28 +0000 +++ b/sql/log_event.h 2011-05-24 14:29:35 +0000 @@ -52,6 +52,7 @@ /* Forward declarations */ class String; typedef ulonglong sql_mode_t; +typedef struct st_db_worker_hash_entry db_worker_hash_entry; #define PREFIX_SQL_LOAD "SQL_LOAD-" @@ -261,14 +262,14 @@ struct sql_ex_info /* The maximum number of updated databases that a status of Query-log-event can carry. - In can redefined still to not be bigger than OVER_MAX_DBS_IN_QUERY_MTS. + In can redefined still to not be bigger than OVER_MAX_DBS_IN_EVENT_MTS. */ -#define MAX_DBS_IN_QUERY_MTS 16 +#define MAX_DBS_IN_EVENT_MTS 16 /* - When the actual number of db:s exceeds MAX_DBS_IN_QUERY_MTS - the value of OVER_MAX_DBS_IN_QUERY_MTS is is put into the mts_updated_dbs status. + When the actual number of db:s exceeds MAX_DBS_IN_EVENT_MTS + the value of OVER_MAX_DBS_IN_EVENT_MTS is is put into the mts_accessed_dbs status. */ -#define OVER_MAX_DBS_IN_QUERY_MTS 254 +#define OVER_MAX_DBS_IN_EVENT_MTS 254 /* Max number of possible extra bytes in a replication event compared to a @@ -286,7 +287,7 @@ struct sql_ex_info 1 + 8 /* type, table_map_for_update */ + \ 1 + 4 /* type, master_data_written */ + \ /* type, db_1, db_2, ... */ \ - 1 + (MAX_DBS_IN_QUERY_MTS * (1 + NAME_LEN)) + \ + 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + \ 1 + 16 + 1 + 60/* type, user_len, user, host_len, host */) #define MAX_LOG_EVENT_HEADER ( /* in order of Query_log_event::write */ \ LOG_EVENT_HEADER_LEN + /* write_header */ \ @@ -1048,6 +1049,10 @@ public: #ifdef MYSQL_SERVER THD* thd; + /** + Partition info associate with event to deliver to MTS event applier + */ + db_worker_hash_entry *mts_assigned_partitions[MAX_DBS_IN_EVENT_MTS]; Log_event(); Log_event(THD* thd_arg, uint16 flags_arg, bool is_transactional); @@ -1245,7 +1250,7 @@ public: */ (get_type_code() == QUERY_EVENT && !starts_group() && !ends_group() && - (mts_number_dbs() == OVER_MAX_DBS_IN_QUERY_MTS)) || + (mts_number_dbs() == OVER_MAX_DBS_IN_EVENT_MTS)) || get_type_code() == START_EVENT_V3 || get_type_code() == STOP_EVENT || @@ -1298,7 +1303,7 @@ public: to be assigned worker; M is the max index of the worker pool. */ - Slave_worker *get_slave_worker_id(Relay_log_info const *rli); + Slave_worker *get_slave_worker_id(Relay_log_info *rli); /** Apply the event to the database. @@ -1887,8 +1892,8 @@ public: number of updated db:s by the query and their names. This info is requested by both Coordinator and Worker. */ - uchar mts_updated_dbs; - char mts_updated_db_names[MAX_DBS_IN_QUERY_MTS][NAME_LEN]; + uchar mts_accessed_dbs; + char mts_accessed_db_names[MAX_DBS_IN_EVENT_MTS][NAME_LEN]; #ifdef MYSQL_SERVER @@ -1898,22 +1903,23 @@ public: /** Returns a list of updated db:s or the default db single item list - in case of over-MAX_DBS_IN_QUERY_MTS actual db:s. + in case of over-MAX_DBS_IN_EVENT_MTS actual db:s. */ virtual List* mts_get_dbs(MEM_ROOT *mem_root) { List *res= new (mem_root) List; - if (mts_updated_dbs == OVER_MAX_DBS_IN_QUERY_MTS) + if (mts_accessed_dbs == OVER_MAX_DBS_IN_EVENT_MTS) res->push_back((char*) get_db()); else - for (uchar i= 0; i < mts_updated_dbs; i++) - res->push_back(mts_updated_db_names[i]); + for (uchar i= 0; i < mts_accessed_dbs; i++) + res->push_back(mts_accessed_db_names[i]); return res; } - virtual uchar mts_number_dbs() { return mts_updated_dbs; } + void attach_temp_tables_worker(THD*); + void detach_temp_tables_worker(THD*); - virtual uchar mts_number_of_updated_dbs() { return mts_updated_dbs; } + virtual uchar mts_number_dbs() { return mts_accessed_dbs; } #ifdef HAVE_REPLICATION void pack_info(Protocol* protocol); === modified file 'sql/rpl_rli.cc' --- a/sql/rpl_rli.cc 2011-05-19 09:36:28 +0000 +++ b/sql/rpl_rli.cc 2011-05-24 14:29:35 +0000 @@ -1626,3 +1626,11 @@ bool Relay_log_info::write_info(Rpl_info DBUG_RETURN(FALSE); } + + +THD* mts_get_coordinator_thd() +{ + return (!active_mi || !active_mi->rli || !active_mi->rli->is_parallel_exec()) ? + NULL : active_mi->rli->info_thd; +} + === modified file 'sql/rpl_rli.h' --- a/sql/rpl_rli.h 2011-05-19 09:36:28 +0000 +++ b/sql/rpl_rli.h 2011-05-24 14:29:35 +0000 @@ -735,4 +735,15 @@ private: bool mysql_show_relaylog_events(THD* thd); +THD* mts_get_coordinator_thd(); + +/** + @param thd a reference to THD + @return TRUE if thd belongs to a Worker thread and FALSE otherwise. +*/ +inline bool mts_is_worker(THD *thd) +{ + return thd->system_thread == SYSTEM_THREAD_SLAVE_WORKER; +} + #endif /* RPL_RLI_H */ === modified file 'sql/rpl_rli_pdb.cc' --- a/sql/rpl_rli_pdb.cc 2011-05-16 19:43:58 +0000 +++ b/sql/rpl_rli_pdb.cc 2011-05-24 14:29:35 +0000 @@ -255,7 +255,7 @@ extern "C" uchar *get_key(const uchar *r { DBUG_ENTER("get_key"); - db_worker *entry=(db_worker *) record; + db_worker_hash_entry *entry=(db_worker_hash_entry *) record; *length= strlen(entry->db); DBUG_PRINT("info", ("get_key %s, %d", entry->db, (int) *length)); @@ -264,12 +264,20 @@ extern "C" uchar *get_key(const uchar *r } -static void free_entry(db_worker *entry) +static void free_entry(db_worker_hash_entry *entry) { + THD *c_thd= current_thd; + DBUG_ENTER("free_entry"); DBUG_PRINT("info", ("free_entry %s, %d", entry->db, (int) strlen(entry->db))); + DBUG_ASSERT(c_thd->system_thread == SYSTEM_THREAD_SLAVE_SQL); + DBUG_ASSERT(entry->usage == 0); + + mts_move_temp_tables_to_thd(c_thd, entry->temporary_tables); + entry->temporary_tables= NULL; + my_free((void *) entry->db); my_free(entry); @@ -291,11 +299,13 @@ bool init_hash_workers(ulong slave_paral DBUG_RETURN (!inited_hash_workers); } -void destroy_hash_workers() +void destroy_hash_workers(Relay_log_info *rli) { DBUG_ENTER("destroy_hash_workers"); if (inited_hash_workers) + { my_hash_free(&mapping_db_to_worker); + } mysql_mutex_destroy(&slave_worker_hash_lock); mysql_cond_destroy(&slave_worker_hash_cond); @@ -303,6 +313,110 @@ void destroy_hash_workers() } /** + Relocating temporary table reference into @c entry location. + Sources can be the coordinator's and the Worker's thd->temporary_tables. + + @param table TABLE instance pointer + @param thd THD instance pointer of the source of relocation + @param entry db_worker_hash_entry instance pointer + + @note thd->temporary_tables can become NULL + + @return the pointer to a table following the unlinked +*/ +TABLE* mts_move_temp_table_to_entry(TABLE *table, THD *thd, + db_worker_hash_entry *entry) +{ + TABLE *ret= table->next; + + if (table->prev) + { + table->prev->next= table->next; + if (table->prev->next) + table->next->prev= table->prev; + } + else + { + /* removing the first item from the list */ + DBUG_ASSERT(table == thd->temporary_tables); + + thd->temporary_tables= table->next; + if (thd->temporary_tables) + table->next->prev= 0; + } + table->next= entry->temporary_tables; + table->prev= 0; + if (table->next) + table->next->prev= table; + entry->temporary_tables= table; + + return ret; +} + + +/** + Relocation of the list of temporary tables to thd->temporary_tables. + + @param thd THD instance pointer of the destination + @param temporary_tables + the source temporary_tables list + + @note destorying references to the source list, if necessary, + is left to the caller. + + @return the post-merge value of thd->temporary_tables. +*/ +TABLE* mts_move_temp_tables_to_thd(THD *thd, TABLE *temporary_tables) +{ + TABLE *table= temporary_tables; + if (!table) + return NULL; + + // accept only the list head + DBUG_ASSERT(!temporary_tables->prev); + + // walk along the source list and associate the tables with thd + do + { + table->in_use= thd; + } while(table->next && (table= table->next)); + + // link the former list against the tail of the source list + if (thd->temporary_tables) + thd->temporary_tables->prev= table; + table->next= thd->temporary_tables; + thd->temporary_tables= temporary_tables; + + return thd->temporary_tables; +} + +/** + Relocating references of temporary tables of a database + of the entry argument from THD into the entry. + + @param thd THD pointer of the source temporary_tables list + @param entry a pointer to db_worker_hash_entry record + containing database descriptor and temporary_tables list. + +*/ +static void move_temp_tables_to_entry(THD* thd, db_worker_hash_entry* entry) +{ + for (TABLE *table= thd->temporary_tables; table;) + { + if (strcmp(table->s->db.str, entry->db) == 0) + { + // table pointer is shifted inside the function + table= mts_move_temp_table_to_entry(table, thd, entry); + } + else + { + table= table->next; + } + } +} + + +/** The function produces a reference to the struct of a Worker that has been or will be engaged to process the @c dbname -keyed partition (D). It checks a local to Coordinator CGAP list first and returns @@ -312,8 +426,8 @@ void destroy_hash_workers() CGAP .= D - and a possible D's Worker id is searched in APH that collects tuples - (P, W_id, U, mutex, cond). + and a possible D's Worker id is searched in Assigne Partition Hash + (APH) that collects tuples (P, W_id, U, mutex, cond). In case not found, W_d := W_c unless W_c is NULL. @@ -339,22 +453,31 @@ void destroy_hash_workers() c. updates the APH record to point to the first Worker (naturally, U := 1), scheduled the event, and goes back into the parallel mode - @note modifies CGAP, APH + @param dbname pointer to c-string containing database name + @param rli pointer to Coordinators relay-log-info instance + @param ptr_entry reference to a pointer to the resulted entry in + the Assigne Partition Hash where + the entry's pointer is stored at return. + + @note modifies CGAP, APH and unlinks @c dbname -keyd temp tables + from C's thd->temporary_tables to move them into the entry record. - @return the pointer to a Worker struct + @return the pointer to a Worker struct */ -Slave_worker *get_slave_worker(const char *dbname, Relay_log_info *rli) +Slave_worker *get_slave_worker(const char *dbname, Relay_log_info *rli, + db_worker_hash_entry **ptr_entry, + bool need_temp_tables) { uint i; - char key[NAME_LEN + 2]; DYNAMIC_ARRAY *workers= &rli->workers; + THD *thd= rli->info_thd; DBUG_ENTER("get_slave_worker"); if (!inited_hash_workers) DBUG_RETURN(NULL); - db_worker *entry= NULL; + db_worker_hash_entry *entry= NULL; my_hash_value_type hash_value; uchar dblength= (uint) strlen(dbname); @@ -363,16 +486,17 @@ Slave_worker *get_slave_worker(const cha // Search in CGAP for (i= 0; i < rli->curr_group_assigned_parts.elements; i++) { - get_dynamic(&rli->curr_group_assigned_parts, (uchar*) key, i); - if ((uchar) key[0] != dblength) + entry= * (db_worker_hash_entry **) + dynamic_array_ptr(&rli->curr_group_assigned_parts, i); + if ((uchar) entry->db_len != dblength) continue; else - if (strncmp(key + 1, const_cast(dbname), dblength) == 0) + if (strncmp(entry->db, const_cast(dbname), dblength) == 0) + { + *ptr_entry= entry; DBUG_RETURN(rli->last_assigned_worker); + } } - key[0]= dblength; - memcpy(key + 1, dbname, dblength + 1); - insert_dynamic(&rli->curr_group_assigned_parts, (uchar*) key); DBUG_PRINT("info", ("Searching for %s, %d", dbname, dblength)); @@ -381,7 +505,7 @@ Slave_worker *get_slave_worker(const cha mysql_mutex_lock(&slave_worker_hash_lock); - entry= (db_worker *) + entry= (db_worker_hash_entry *) my_hash_search_using_hash_value(&mapping_db_to_worker, hash_value, (uchar*) dbname, dblength); if (!entry) @@ -395,17 +519,6 @@ Slave_worker *get_slave_worker(const cha my_bool ret; char *db= NULL; - if (mapping_db_to_worker.records > opt_mts_partition_hash_soft_max) - { - /* remove zero-usage (todo: relatively rare scheduled) records */ - for (uint i= 0; i < mapping_db_to_worker.records; i++) - { - db_worker *entry= (db_worker*) my_hash_element(&mapping_db_to_worker, i); - if (entry->usage == 0) - my_hash_delete(&mapping_db_to_worker, (uchar*) entry); - } - } - mysql_mutex_unlock(&slave_worker_hash_lock); DBUG_PRINT("info", ("Inserting %s, %d", dbname, dblength)); @@ -415,14 +528,16 @@ Slave_worker *get_slave_worker(const cha */ if (!(db= (char *) my_malloc((size_t) dblength + 1, MYF(0)))) goto err; - if (!(entry= (db_worker *) my_malloc(sizeof(db_worker), MYF(0)))) + if (!(entry= (db_worker_hash_entry *) my_malloc(sizeof(db_worker_hash_entry), MYF(0)))) { my_free(db); goto err; } strmov(db, dbname); entry->db= db; + entry->db_len= strlen(db); entry->usage= 1; + entry->temporary_tables= NULL; /* Unless \exists the last assigned Worker, get a free worker based on a policy described in the function get_least_occupied_worker(). @@ -430,8 +545,33 @@ Slave_worker *get_slave_worker(const cha entry->worker= !rli->last_assigned_worker ? get_least_occupied_worker(workers) : rli->last_assigned_worker; entry->worker->usage_partition++; + /* + relocation belonging to db temp tables from C to W via entry + */ + if (need_temp_tables) + move_temp_tables_to_entry(thd, entry); mysql_mutex_lock(&slave_worker_hash_lock); + + if (mapping_db_to_worker.records > opt_mts_partition_hash_soft_max) + { + /* remove zero-usage (todo: rare or long ago scheduled) records */ + for (uint i= 0; i < mapping_db_to_worker.records; i++) + { + db_worker_hash_entry *entry= + (db_worker_hash_entry*) my_hash_element(&mapping_db_to_worker, i); + if (entry->usage == 0) + { + DBUG_ASSERT(!entry->temporary_tables || !entry->temporary_tables->prev); + DBUG_ASSERT(!thd->temporary_tables || !thd->temporary_tables->prev); + + mts_move_temp_tables_to_thd(thd, entry->temporary_tables); + entry->temporary_tables= NULL; + my_hash_delete(&mapping_db_to_worker, (uchar*) entry); + } + } + } + ret= my_hash_insert(&mapping_db_to_worker, (uchar*) entry); mysql_mutex_unlock(&slave_worker_hash_lock); if (ret) @@ -472,7 +612,6 @@ Slave_worker *get_slave_worker(const cha // D-partition represents // the hashing conflict and is handled as the following: - THD *thd= rli->info_thd; const char *proc_info; const char info_format[]= "Waiting for Slave Worker %d to release partition `%s`"; @@ -480,7 +619,7 @@ Slave_worker *get_slave_worker(const cha NAME_LEN + 1]; DBUG_ASSERT(rli->last_assigned_worker != NULL && - rli->curr_group_assigned_parts.elements > 1); + rli->curr_group_assigned_parts.elements > 0); // future assignenment and marking at the same time entry->worker= rli->last_assigned_worker; @@ -497,15 +636,38 @@ Slave_worker *get_slave_worker(const cha entry->usage= 1; entry->worker->usage_partition++; + } + if (entry->usage == 1 && need_temp_tables) + { + if (!entry->temporary_tables) + { + move_temp_tables_to_entry(thd, entry); + } +#ifndef DBUG_OFF + else + { + for (TABLE *table= thd->temporary_tables; table; table= table->next) + { + DBUG_ASSERT(0 != strcmp(table->s->db.str, entry->db)); + } + } +#endif } + mysql_mutex_unlock(&slave_worker_hash_lock); } + DBUG_ASSERT(entry); + err: if (entry) - DBUG_PRINT("info", ("Updating %s with worker %lu", entry->db, entry->worker->id)); - + { + DBUG_PRINT("info", + ("Updating %s with worker %lu", entry->db, entry->worker->id)); + insert_dynamic(&rli->curr_group_assigned_parts, (uchar*) &entry); + *ptr_entry= entry; + } DBUG_RETURN(entry ? entry->worker : NULL); } @@ -518,16 +680,16 @@ err: Slave_worker *get_least_occupied_worker(DYNAMIC_ARRAY *ws) { ulong usage= ULONG_MAX; - Slave_worker *current_worker= NULL, *worker= NULL; + Slave_worker **ptr_current_worker= NULL, *worker= NULL; ulong i= 0; for (i= 0; i< ws->elements; i++) { - get_dynamic(ws, (uchar*) ¤t_worker, i); - if (current_worker->usage_partition <= usage) + ptr_current_worker= (Slave_worker **) dynamic_array_ptr(ws, i); + if ((*ptr_current_worker)->usage_partition <= usage) { - worker= current_worker; - usage= current_worker->usage_partition; + worker= *ptr_current_worker; + usage= (*ptr_current_worker)->usage_partition; } } @@ -550,11 +712,9 @@ Slave_worker *get_least_occupied_worker( void Slave_worker::slave_worker_ends_group(Log_event* ev, int error) { - int i; - ulong gaq_idx= ev->mts_group_cnt; - if (!error) { + ulong gaq_idx= ev->mts_group_cnt; Slave_job_group *ptr_g= (Slave_job_group *) dynamic_array_ptr(&c_rli->gaq->Q, gaq_idx); @@ -588,11 +748,13 @@ void Slave_worker::slave_worker_ends_gro last_group_done_index= gaq_idx; } - // cleanup relating to the last executed group regardless of error + /* + Cleanup relating to the last executed group regardless of error. + */ - for (i= curr_group_exec_parts->dynamic_ids.elements; i > 0; i--) + for (int i= curr_group_exec_parts->dynamic_ids.elements; i > 0; i--) { - db_worker *entry= NULL; + db_worker_hash_entry *entry= NULL; my_hash_value_type hash_value; char key[NAME_LEN + 2]; @@ -601,7 +763,7 @@ void Slave_worker::slave_worker_ends_gro mysql_mutex_lock(&slave_worker_hash_lock); - entry= (db_worker *) + entry= (db_worker_hash_entry *) my_hash_search_using_hash_value(&mapping_db_to_worker, hash_value, (uchar*) key + 1, key[0]); @@ -614,6 +776,14 @@ void Slave_worker::slave_worker_ends_gro if (entry->usage == 0) { + /* + The detached entry's temp table list, possibly updated, remains + with the entry at least until time Coordinator will deallocate it + from the hash, that is either due to stop or extra size of the hash. + */ + + DBUG_ASSERT(this->info_thd->temporary_tables == 0); + usage_partition--; if (entry->worker != this) // Coordinator is waiting mysql_cond_signal(&slave_worker_hash_cond); @@ -875,27 +1045,49 @@ void Slave_worker::report(loglevel level va_end(vargs); } +/** + Function is called by Coordinator when it identified an event + requiring sequential execution. + Creating sequential context for the event includes waiting + for the assigned to Workers tasks to be completed and their + resources such as temporary tables be returned to Coordinator's + repository. + + @param rli Relay_log_info instance of Coordinator + @param ignore Optional Worker instance pointer if the sequential context + is established due for the ignore Worker. Its resources + are to be retained. + + @note Resources that are not occupied by Workers such as + a list of temporary tables held in unused (zero-usage) records + of APH are relocated to the Coordinator placeholder. + + @return non-negative number of released by Workers partitions + (one partition by one Worker can count multiple times). +*/ + int wait_for_workers_to_finish(Relay_log_info const *rli, Slave_worker *ignore) { uint ret= 0; HASH *hash= &mapping_db_to_worker; + THD *thd= rli->info_thd; + const char info_format[]= + "Waiting for Slave Worker %d to release partition `%s`"; for (uint i= 0, ret= 0; i < hash->records; i++) { - db_worker *entry; - THD *thd= rli->info_thd; + db_worker_hash_entry *entry; const char *proc_info; - const char info_format[]= - "Waiting for Slave Worker %d to release partition `%s`"; char wait_info[sizeof(info_format) + 4*sizeof(entry->worker->id) + NAME_LEN + 1]; mysql_mutex_lock(&slave_worker_hash_lock); - entry= (db_worker*) my_hash_element(hash, i); + entry= (db_worker_hash_entry*) my_hash_element(hash, i); DBUG_ASSERT(entry); - if (ignore && entry->worker == ignore) + // the ignore Worker retains its active resources + if (ignore && entry->worker == ignore && entry->usage > 0) { mysql_mutex_unlock(&slave_worker_hash_lock); continue; @@ -904,7 +1096,7 @@ int wait_for_workers_to_finish(Relay_log if (entry->usage > 0) { sprintf(wait_info, info_format, entry->worker->id, entry->db); - entry->worker= NULL; + entry->worker= NULL; // mark Worker to signal when usage drops to 0 proc_info= thd->enter_cond(&slave_worker_hash_cond, &slave_worker_hash_lock, wait_info); @@ -916,6 +1108,9 @@ int wait_for_workers_to_finish(Relay_log } else { + // resources relocation + mts_move_temp_tables_to_thd(thd, entry->temporary_tables); + entry->temporary_tables= NULL; mysql_mutex_unlock(&slave_worker_hash_lock); } } === modified file 'sql/rpl_rli_pdb.h' --- a/sql/rpl_rli_pdb.h 2011-05-16 19:43:58 +0000 +++ b/sql/rpl_rli_pdb.h 2011-05-24 14:29:35 +0000 @@ -8,22 +8,33 @@ #include /* APH entry */ -struct db_worker +typedef struct st_db_worker_hash_entry { + uint db_len; const char *db; Slave_worker *worker; ulong usage; + /* + The list of temp tables belonging to @ db database is + attached to an assigned @c worker to become its thd->temporary_tables. + The list is updated with every ddl incl CREATE, DROP. + It is removed from the entry and merged to the coordinator's thd->temporary_tables + in case of events: slave stops, the db-to-worker hash oversize. + */ + TABLE* volatile temporary_tables; - // todo: relax concurrency after making APH mutex/cond pair has worked - // pthread_mutex_t - // pthread_cond_t - // timestamp updated_at; + /* todo: relax concurrency after making APH mutex/cond pair has worked + pthread_mutex_t + pthread_cond_t + timestamp updated_at; */ -} typedef db_worker; +} db_worker_hash_entry; bool init_hash_workers(ulong slave_parallel_workers); -void destroy_hash_workers(); -Slave_worker *get_slave_worker(const char *dbname, Relay_log_info *rli); +void destroy_hash_workers(Relay_log_info*); +Slave_worker *get_slave_worker(const char *dbname, Relay_log_info *rli, + db_worker_hash_entry **ptr_entry, + bool need_temp_tables); Slave_worker *get_least_occupied_worker(DYNAMIC_ARRAY *workers); int wait_for_workers_to_finish(Relay_log_info const *rli, Slave_worker *ignore= NULL); @@ -282,6 +293,9 @@ private: Slave_worker(const Slave_worker& info); }; +TABLE* mts_move_temp_table_to_entry(TABLE*, THD*, db_worker_hash_entry*); +TABLE* mts_move_temp_tables_to_thd(THD*, TABLE*); + extern PSI_mutex_key *key_mutex_slave_parallel_worker; extern PSI_mutex_key key_mutex_slave_parallel_pend_jobs; === modified file 'sql/rpl_slave.cc' --- a/sql/rpl_slave.cc 2011-05-19 09:36:28 +0000 +++ b/sql/rpl_slave.cc 2011-05-24 14:29:35 +0000 @@ -143,7 +143,7 @@ failed read" }; -typedef enum { SLAVE_THD_IO, SLAVE_THD_SQL, SLAVE_THD_CHECKPOINT } SLAVE_THD_TYPE; +typedef enum { SLAVE_THD_IO, SLAVE_THD_SQL, SLAVE_THD_CHECKPOINT, SLAVE_THD_WORKER } SLAVE_THD_TYPE; static int process_io_rotate(Master_info* mi, Rotate_log_event* rev); static int process_io_create_file(Master_info* mi, Create_file_log_event* cev); @@ -2387,7 +2387,8 @@ static int init_slave_thread(THD* thd, S #if !defined(DBUG_OFF) int simulate_error= 0; #endif - thd->system_thread = (thd_type == SLAVE_THD_SQL) ? + thd->system_thread= (thd_type == SLAVE_THD_WORKER) ? + SYSTEM_THREAD_SLAVE_WORKER : (thd_type == SLAVE_THD_SQL) ? SYSTEM_THREAD_SLAVE_SQL : SYSTEM_THREAD_SLAVE_IO; thd->security_ctx->skip_grants(); my_net_init(&thd->net, 0); @@ -3707,7 +3708,7 @@ pthread_handler_t handle_slave_worker(vo thd->thread_stack = (char*)&thd; pthread_detach_this_thread(); - if (init_slave_thread(thd, SLAVE_THD_SQL)) // todo: make thd->sys_thr= worker + if (init_slave_thread(thd, SLAVE_THD_WORKER)) { // todo make SQL thread killed sql_print_error("Failed during slave worker initialization"); @@ -4282,7 +4283,8 @@ int slave_start_workers(Relay_log_info * rli->init_workers(n); // CGAP dynarray holds id:s of partitions of the Current being executed Group - my_init_dynamic_array(&rli->curr_group_assigned_parts, 1 + NAME_LEN + 1, SLAVE_INIT_DBS_IN_GROUP, 1); + my_init_dynamic_array(&rli->curr_group_assigned_parts, sizeof(db_worker_hash_entry*), + SLAVE_INIT_DBS_IN_GROUP, 1); rli->last_assigned_worker= NULL; /* associated with curr_group_assigned */ my_init_dynamic_array(&rli->curr_group_da, sizeof(Log_event*), 8, 2); // Least_occupied_workers array to hold items size of Slave_jobs_queue::len @@ -4318,7 +4320,7 @@ int slave_start_workers(Relay_log_info * dyn memory to consume by Coordinator per event */ init_alloc_root(&rli->mts_coor_mem_root, NAME_LEN, - (MAX_DBS_IN_QUERY_MTS / 2) * NAME_LEN); + (MAX_DBS_IN_EVENT_MTS / 2) * NAME_LEN); for (i= 0; i < n; i++) { @@ -4411,7 +4413,7 @@ void slave_stop_workers(Relay_log_info * DBUG_ASSERT(rli->pending_jobs == 0); DBUG_ASSERT(rli->mts_pending_jobs_size == 0); - destroy_hash_workers(); + destroy_hash_workers(rli); delete rli->gaq; delete_dynamic(&rli->least_occupied_workers); // least occupied delete_dynamic(&rli->curr_group_da); // GCDA @@ -7084,44 +7086,6 @@ err: } -/******************************************/ -/* MTS temporary table support section */ - - -/** - @return a mutex that guards access to the SQL thread controlled - temporary tables list. -*/ -mysql_mutex_t* mts_get_temp_table_mutex() -{ - return &active_mi->rli->mts_temp_tables_lock; -} - -/** - @return a reference to THD of the Coordinator thread or NULL - in case of no replication is set up or it's in the sequential mode. -*/ -THD* mts_get_coordinator_thd() -{ - return (!active_mi || !active_mi->rli || !active_mi->rli->is_parallel_exec()) ? - NULL : active_mi->rli->info_thd; -} - -/** - TODO: exploint new slave_worker system thread type property - - @param thd a reference to THD - - @return TRUE if thd belongs to a Worker thread and FALSE otherwise. -*/ -bool mts_is_worker(THD *thd) -{ - return - thd->slave_thread && active_mi->rli->info_thd != thd; -} - -/* end of MTS temp table support section */ - /** @} (end of group Replication) */ === modified file 'sql/rpl_slave.h' --- a/sql/rpl_slave.h 2011-02-27 17:35:25 +0000 +++ b/sql/rpl_slave.h 2011-05-24 14:29:35 +0000 @@ -242,11 +242,6 @@ extern I_List threads; bool mts_recovery_groups(Relay_log_info *rli, MY_BITMAP *groups); bool mts_checkpoint_routine(Relay_log_info *rli, ulonglong period, bool force, bool locked); -THD* mts_get_coordinator_thd(); -THD* mts_get_worker_thd(); -mysql_mutex_t* mts_get_temp_table_mutex(); -bool mts_is_worker(THD *thd); - #endif /* HAVE_REPLICATION */ /* masks for start/stop operations on io and sql slave threads */ === modified file 'sql/rpl_utility.h' --- a/sql/rpl_utility.h 2010-09-09 18:43:16 +0000 +++ b/sql/rpl_utility.h 2011-05-24 14:29:35 +0000 @@ -27,16 +27,6 @@ #endif #include "mysql_com.h" -/* - mts-II prototype macros (once were a part of my_bitmap.h...) -*/ -#define bit_is_set(I,B) (sizeof(I) * CHAR_BIT > (B) ? \ - (((I) & (ULL(1) << (B))) == 0 ? 0 : 1) : -1) -#define bit_do_set(I,B) (sizeof(I) * CHAR_BIT > (B) ? \ - ((I) |= (ULL(1) << (B)), 1) : -1) -#define bit_do_clear(I,B) (sizeof(I) * CHAR_BIT > (B) ? \ - ((I) &= ~(ULL(1) << (B)), 0) : -1) - class Relay_log_info; === modified file 'sql/sql_base.cc' --- a/sql/sql_base.cc 2011-02-27 17:35:25 +0000 +++ b/sql/sql_base.cc 2011-05-24 14:29:35 +0000 @@ -40,7 +40,6 @@ #include "sql_handler.h" // mysql_ha_flush #include "sql_partition.h" // ALTER_PARTITION_PARAM_TYPE #include "log_event.h" // Query_log_event -#include "rpl_slave.h" // MTS temp table support #include "sql_select.h" #include "sp_head.h" #include "sp.h" @@ -59,6 +58,7 @@ #include #endif + bool No_such_table_error_handler::handle_condition(THD *, uint sql_errno, @@ -1192,25 +1192,11 @@ bool close_cached_connection_tables(THD static void mark_temp_tables_as_free_for_reuse(THD *thd) { -#ifndef EMBEDDED_LIBRARY - bool mts_slave= mts_is_worker(thd); - TABLE *temporary_tables= mts_slave ? - mts_get_coordinator_thd()->temporary_tables : thd->temporary_tables; - if (mts_slave) - mysql_mutex_lock(mts_get_temp_table_mutex()); -#else - TABLE *temporary_tables= thd->temporary_tables; -#endif - - for (TABLE *table= temporary_tables; table ; table=table->next) + for (TABLE *table= thd->temporary_tables ; table ; table= table->next) { if ((table->query_id == thd->query_id) && ! table->open_by_handler) mark_tmp_table_for_reuse(table); } -#ifndef EMBEDDED_LIBRARY - if (mts_slave) - mysql_mutex_unlock(mts_get_temp_table_mutex()); -#endif } @@ -1602,8 +1588,6 @@ bool close_temporary_tables(THD *thd) bool was_quote_show= TRUE; bool error= 0; - DBUG_ASSERT(!thd->slave_thread || thd->temporary_tables == NULL); - if (!thd->temporary_tables) DBUG_RETURN(FALSE); @@ -2041,29 +2025,16 @@ TABLE *find_temporary_table(THD *thd, const char *table_key, uint table_key_length) { - TABLE *table= NULL; -#ifndef EMBEDDED_LIBRARY - bool mts_slave= mts_is_worker(thd); - TABLE *temporary_tables= mts_slave ? - mts_get_coordinator_thd()->temporary_tables : thd->temporary_tables; - if (mts_slave) - mysql_mutex_lock(mts_get_temp_table_mutex()); -#else - TABLE *temporary_tables= thd->temporary_tables; -#endif - for (table= temporary_tables; table; table= table->next) + for (TABLE *table= thd->temporary_tables; table; table= table->next) { if (table->s->table_cache_key.length == table_key_length && !memcmp(table->s->table_cache_key.str, table_key, table_key_length)) { - break; + return table; } } -#ifndef EMBEDDED_LIBRARY - if (mts_slave) - mysql_mutex_unlock(mts_get_temp_table_mutex()); -#endif - return table; + + return NULL; } @@ -2101,11 +2072,6 @@ TABLE *find_temporary_table(THD *thd, int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans) { TABLE *table; -#ifndef EMBEDDED_LIBRARY - bool mts_slave= mts_is_worker(thd); -#endif - THD *thd_temp= NULL; - DBUG_ENTER("drop_temporary_table"); DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'", table_list->db, table_list->table_name)); @@ -2128,26 +2094,7 @@ int drop_temporary_table(THD *thd, TABLE unlock the table and remove the table from this list. */ mysql_lock_remove(thd, thd->lock, table); - -#ifndef EMBEDDED_LIBRARY - if (mts_slave) - { - thd_temp= mts_get_coordinator_thd(); - mysql_mutex_lock(mts_get_temp_table_mutex()); - } - else -#endif - { - thd_temp= thd; - } - - close_temporary_table(thd_temp, table, 1, 1); - -#ifndef EMBEDDED_LIBRARY - if (mts_slave) - mysql_mutex_unlock(mts_get_temp_table_mutex()); -#endif - + close_temporary_table(thd, table, 1, 1); DBUG_RETURN(0); } @@ -2178,7 +2125,7 @@ void close_temporary_table(THD *thd, TAB passing non-zero value to end_slave via rli->save_temporary_tables when no temp tables opened, see an invariant below. */ - thd->temporary_tables= table->next; // mts: see drop_temporary_table() + thd->temporary_tables= table->next; if (thd->temporary_tables) table->next->prev= 0; } @@ -2684,17 +2631,7 @@ bool open_table(THD *thd, TABLE_LIST *ta if (table_list->open_type != OT_BASE_ONLY && ! (flags & MYSQL_OPEN_SKIP_TEMPORARY)) { -#ifndef EMBEDDED_LIBRARY - bool mts_slave= mts_is_worker(thd); - TABLE *temporary_tables= mts_slave ? - mts_get_coordinator_thd()->temporary_tables : thd->temporary_tables; - if (mts_slave) - mysql_mutex_lock(mts_get_temp_table_mutex()); -#else - TABLE *temporary_tables= thd->temporary_tables; -#endif - - for (table= temporary_tables; table ; table=table->next) + for (table= thd->temporary_tables; table ; table=table->next) { if (table->s->table_cache_key.length == key_length + TMP_TABLE_KEY_EXTRA && @@ -2714,26 +2651,14 @@ bool open_table(THD *thd, TABLE_LIST *ta (ulong) table->query_id, (uint) thd->server_id, (ulong) thd->variables.pseudo_thread_id)); my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); -#ifndef EMBEDDED_LIBRARY - if (mts_slave) - mysql_mutex_unlock(mts_get_temp_table_mutex()); -#endif DBUG_RETURN(TRUE); } table->query_id= thd->query_id; thd->thread_specific_used= TRUE; DBUG_PRINT("info",("Using temporary table")); -#ifndef EMBEDDED_LIBRARY - if (mts_slave) - mysql_mutex_unlock(mts_get_temp_table_mutex()); -#endif goto reset; } } -#ifndef EMBEDDED_LIBRARY - if (mts_slave) - mysql_mutex_unlock(mts_get_temp_table_mutex()); -#endif } if (table_list->open_type == OT_TEMPORARY_ONLY || @@ -5926,28 +5851,14 @@ TABLE *open_table_uncached(THD *thd, con if (add_to_temporary_tables_list) { -#ifndef EMBEDDED_LIBRARY - TABLE **ptr_temporary_tables; - bool mts_slave= mts_is_worker(thd); - ptr_temporary_tables= mts_slave? - &mts_get_coordinator_thd()->temporary_tables : &thd->temporary_tables; - if (mts_slave) - mysql_mutex_lock(mts_get_temp_table_mutex()); -#else - TABLE **ptr_temporary_tables= &thd->temporary_tables; -#endif /* growing temp list at the head */ - tmp_table->next= *ptr_temporary_tables; + tmp_table->next= thd->temporary_tables; if (tmp_table->next) tmp_table->next->prev= tmp_table; - *ptr_temporary_tables= tmp_table; - (*ptr_temporary_tables)->prev= 0; + thd->temporary_tables= tmp_table; + thd->temporary_tables->prev= 0; if (thd->slave_thread) slave_open_temp_tables++; -#ifndef EMBEDDED_LIBRARY - if (mts_slave) - mysql_mutex_unlock(mts_get_temp_table_mutex()); -#endif } tmp_table->pos_in_table_list= 0; DBUG_PRINT("tmptable", ("opened table: '%s'.'%s' 0x%lx", tmp_table->s->db.str, === modified file 'sql/sql_class.cc' --- a/sql/sql_class.cc 2011-02-27 17:35:25 +0000 +++ b/sql/sql_class.cc 2011-05-24 14:29:35 +0000 @@ -503,7 +503,7 @@ THD::THD() user_time(0), in_sub_stmt(0), binlog_unsafe_warning_flags(0), binlog_table_maps(0), - binlog_updated_db_names(NULL), + binlog_accessed_db_names(NULL), table_map_for_update(0), arg_of_last_insert_id_function(FALSE), first_successful_insert_id_in_prev_stmt(0), @@ -1398,7 +1398,7 @@ void THD::cleanup_after_query() stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0; auto_inc_intervals_in_cur_stmt_for_binlog.empty(); rand_used= 0; - binlog_updated_db_names= NULL; + binlog_accessed_db_names= NULL; } if (first_successful_insert_id_in_cur_stmt > 0) { === modified file 'sql/sql_class.h' --- a/sql/sql_class.h 2011-02-27 17:35:25 +0000 +++ b/sql/sql_class.h 2011-05-24 14:29:35 +0000 @@ -1724,7 +1724,7 @@ private: /* MTS: db names listing to be updated by the query databases */ - List *binlog_updated_db_names; + List *binlog_accessed_db_names; public: void issue_unsafe_warnings(); @@ -1737,25 +1737,25 @@ public: } /* - MTS: accessor to binlog_updated_db_names list + MTS: accessor to binlog_accessed_db_names list */ - List * get_binlog_updated_db_names() { - return binlog_updated_db_names; + List * get_binlog_accessed_db_names() { + return binlog_accessed_db_names; } /* - MTS: initializer of binlog_updated_db_names list + MTS: initializer of binlog_accessed_db_names list */ - void set_binlog_updated_db_names(List* arg) + void set_binlog_accessed_db_names(List* arg) { - binlog_updated_db_names= arg; + binlog_accessed_db_names= arg; } /* - MTS: resetter of binlog_updated_db_names list normally + MTS: resetter of binlog_accessed_db_names list normally at the end of the query execution */ - void clear_binlog_updated_db_names() { binlog_updated_db_names= NULL; } + void clear_binlog_accessed_db_names() { binlog_accessed_db_names= NULL; } /* MTS: method inserts a new unique name into binlog_updated_dbs */ void add_to_binlog_updated_dbs(const char *db); @@ -1766,8 +1766,8 @@ public: */ void add_one_db_to_binlog_updated_dbs(const char *db) { - set_binlog_updated_db_names(new List); - binlog_updated_db_names->push_back(strdup_root(mem_root, db)); + set_binlog_accessed_db_names(new List); + binlog_accessed_db_names->push_back(strdup_root(mem_root, db)); } #endif /* MYSQL_CLIENT */ === modified file 'sql/sql_rename.cc' --- a/sql/sql_rename.cc 2011-02-27 17:35:25 +0000 +++ b/sql/sql_rename.cc 2011-05-24 14:29:35 +0000 @@ -318,9 +318,9 @@ do_rename(THD *thd, TABLE_LIST *ren_tabl break; } - if (!thd->get_binlog_updated_db_names()) + if (!thd->get_binlog_accessed_db_names()) { - thd->set_binlog_updated_db_names(new List); + thd->set_binlog_accessed_db_names(new List); } thd->add_to_binlog_updated_dbs(ren_table->db); thd->add_to_binlog_updated_dbs(new_db); === modified file 'sql/sql_table.cc' --- a/sql/sql_table.cc 2011-02-27 17:35:25 +0000 +++ b/sql/sql_table.cc 2011-05-24 14:29:35 +0000 @@ -2237,9 +2237,9 @@ int mysql_rm_table_no_locks(THD *thd, TA table->mdl_request.ticket != NULL)); /* MTS: similarly to decide_logging_format() gathering of the db names */ - if (!thd->get_binlog_updated_db_names()) + if (!thd->get_binlog_accessed_db_names()) { - thd->set_binlog_updated_db_names(new List); + thd->set_binlog_accessed_db_names(new List); } thd->add_to_binlog_updated_dbs(table->db); @@ -5953,9 +5953,9 @@ bool mysql_alter_table(THD *thd,char *ne if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db)) new_db= db; - if (!thd->get_binlog_updated_db_names()) + if (!thd->get_binlog_accessed_db_names()) { - thd->set_binlog_updated_db_names(new List); + thd->set_binlog_accessed_db_names(new List); } thd->add_to_binlog_updated_dbs(db); if (new_db != db) === modified file 'sql/sql_view.cc' --- a/sql/sql_view.cc 2011-02-27 17:35:25 +0000 +++ b/sql/sql_view.cc 2011-05-24 14:29:35 +0000 @@ -1683,9 +1683,9 @@ bool mysql_drop_view(THD *thd, TABLE_LIS } continue; } - if (!thd->get_binlog_updated_db_names()) + if (!thd->get_binlog_accessed_db_names()) { - thd->set_binlog_updated_db_names(new List); + thd->set_binlog_accessed_db_names(new List); } thd->add_to_binlog_updated_dbs(view->db); if (mysql_file_delete(key_file_frm, path, MYF(MY_WME))) --===============1394145103== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/andrei.elkin@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: andrei.elkin@stripped # target_branch: file:///home/andrei/MySQL/BZR/2a-23May/WL/mysql-next-\ # mr-wl5569/ # testament_sha1: 4c41f8e7bee5b3a4e97e765820a7308117f31de8 # timestamp: 2011-05-24 17:29:46 +0300 # source_branch: file:///home/andrei/MySQL/BZR/2a-23May/mysql-trunk/ # base_revision_id: andrei.elkin@stripped\ # zldw938goh2lr2in # # Begin bundle IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWQlxT2IAH5N/gH//5An7//// /+//7v////5gNP7vTvtvl3a+++45x5s70AJ76wapXQ3YXu23nS897uvvMd673VJ9fd329o9VkDvu 6jzYPd3LZb6O7MYw7slzc61ffJ82bNBTm2u20G+3CO20vn3ntj3hDzvthferrh3GJdOhdr1r2Uex rtU+7p7S3sO7Do4uua43meuZhve3m91d3euVnsNOmvCSIQBGBACJhKfoDIKfpTU/VPynpT1Gn6kf qmxDSA0eoGaT0T0GgBBCExGqeSejQJNlA0ANAAAAAAAaAA0xCCSEZCeU1NMNRkekZkJ6mmJoDQ9Q A0AAA0ABJpRIJkAJqekjaHoiYaaTNJ4hBoDajQ0GgAA0GhoIlEE00ACJgmEyNARlMyagzUQyfpI9 MQhoABoDBIiBAEQBDUmmap+p6j1Tan6U2pkNAAA0NDQAaAAHRxI9w9zQAD+qIAP8H1et/dVpfKea jSXealcPPUXKHxf2xAHwVUEx4jkj9XfVBYM87Z8nR7XQl2/IF5K53xas6j3eP/j1fPZU6Vtzz05M 0xbBT6ts8aX5bOBJJBIGO3Xca91ulzfod/+PN7uP73q0T8JRHSggQcoKkgTj0kLIDNUgSPiXouh1 LR6Rai0oJNxEQ0FDW+Wv7+P3txl93/We7mJ6sZP0n7pq8dK1fGv+HRKduUtZUekGjrpY+v76FL2t 4RGbGw8/WQHt885LtXHG85TPOroUVaVJ7kh6yBVUf8zFyVq1yNcj6p6yE/up8TInKG6fuavHx3F7 XUDMGntsIFOM8G87z0uO0cqBQbBW7HJ7QOJtxDhOz+/BZY/f49zhvuOdqPSvrniwpYAgG6ExINk8 YhDXrVfjJwpK8QP12weFXHaglZ4z68bMKawGY6sPcx5uM8G102xfQ8zL9LKaLFjeRr8jGXqYtnbX 4z0yNX8v2ODisNs8/rc+p8r2HyTOlYbKP2YKEq+dtiVA4NBlHpO7QJzYkmwSiOrl3wjHyjQoonGv M/Fjvy/t2lFRUQqzY99hgr2oYkkzDrnGxeTMgtICfWs+Gevw9O3jb6qwrPd7/LRBIt9XfRVTSIdH k3WAOMC6AmuC0gV1URNejPVR1xb4BpmwwmWQ5GQ1oYYsWGy0k0Q5EkEghvMAdgpLTSiymqCTjAXC HM1VCjCmriDEyjRwRc4mRk8rQwUSqmzpg4sYKpYy608aaqhg2YMuXVp8a1vGNusDKnT3Os6uzgoh YaFKzN3eLtysOSpWTUzNKr6ccdz5FXfQo7XVJXPavHvCTHaqCHm8uXs6uyQ65fbsoapd20QRDFgC ISCMEbIiPZEB/6qVqH7IqzsZo1gSGoYAaLqKGO7ZNjMCQ52WBOFpGJfR9Q26BoKKDNjLOG2P07CV gFp8eC4ZYUYupxE3MKkhwHJvunoeOewrtjTPQeJ/tbROcsf6JBhwLGQst+j6cY3ImOmCnaAosMrd EELGLh9c3smkUoYjC/zQv3ywROmGnv6M44d0lCn0NDBYzMaP2+tIBctd5q6pr2Z3XEkP6VDkzb1D uSAqAoiKSCikWALJBYKCySKLIKSIwFgCyBzcinosqAejDiAz4IiIEesfEvGdt+vPj7vinBFtl1NM 1NUolkNRRC3ietCcfBax5y6jFYijaHDUkrCzVRkgWGvGcAkit2u9dQyJe3kgaS4I2eMqM1bvjTDO zDWqiBGAjJ1eKnaYy9sHx79LjWdxweDgjZG8qAsEGUxPJCTJ5EMjZZRoqlW0JMDaCweYgYpDJJxl aLRQzuw6OzOTTRgzh5MDQIZZJiqDYGeIAuxu8LNLdbRidbDMPE6E4JIzeJmIvWarDuMGiM5QbQFU oAZzLmbb2DvUu5lurSMUaIGSGcEbIlYZLIFGNGzRVIbJyhgs5ehQu0LJ2YyZ0q0rKIZhzpgrbFum jkxBLOwFWRimcmTpzpYczRlLsV0gQUbw9yMcKizJs2aM8ChWHtg0eIUWMUYdIUWf+Hh7R6jHiBgv dYb+weWPOYoQaugUBggYPRq4MGpiYA8SnPeAC6ehvUeM5ed3nT+2JLsalmk7CjW91NvPGxNH0Ofy S9z4WMqOqL8NNVZv7Po+jXIVmJ5uAnrB7mVZXOJsdZLz7aGpYO7/OH46X9jO3c/objSiTcmfN3YO T+TuypIQjONPpwW+vnjHiDHlPTkjjHa6Ds4HjOj3Hv0Db0X2GPzay+p32szweJCex15gfOrem93r 9ffgc4CS79MZtessfE2J90pdPK7F1Kuuj2PNzVHQJHYzdU8InrDx7LyQU4/H23EZQcqM9DDHxNCy dDCrvv/2HNusTrIt1oeZ90+D1+i2GR0MY7tATwPDpqr/FbNkx6ueFECjCRzbm8wEeKJ+IOhIkdvz EhNtnJo9XX1cLUabXOp94m+9SvbV0pXVnb0gLqqysl0br5DwfmwvwQ5IgPG6KNkA3AoTCmnhsj6Y ZaIfhax9aL9/b4ZZmZ5cBoUrY+6lyDSorNsYN9xAB/Q64sLaZ1ZBfFlZidm6j5RZqOOUhxWnyRBi 3FL263LDFPTZcbINYJBVHs6LHrG+V2EceJupnWNvKQobMIfb3xLBm5za2Tiv2csHh2VzKf89x3E1 LEY3ud8sYQxybpwPkEyoUGJpd8+nvEH7enyiIDx58h46u4e3zrubHu4pjJR7Dg95DqgkFhEUA9pM TXbWDWnElVfCluZF7Pf6n8Djj1Z102LXkRAd94NieVAkyYx4GJ7j9g9Oy9d1eVew+bctRkrNloB8 hNZ40SppbZhCWXQ+G5cJh/I67H1KPVlCp78gCDDbxmO4izjjjXCmfuZYPt1Ykb7buKqCUHa91XW5 6WCsMmIGDiPP5xXh0O+Dpsg7Rt7swBazabBg3rD3J453p9lw3COBGLMyvhSSi9hXPCPh2CQZS3tj VbajGVUN850vymRuREhOo7HhxJkJNYLgj517I4Hv00DjclQNTwl7jOR6VF79ksV3t1yl3hrjejHH kSWQp2adWjJcmby2Tjm+/NXE/sCrLKsIzMTSEmN0z4o6m03VNpQQ2Lwq4mB1+RL4PzZvjloVH3Vw bSwz8CpA9sHt29vk6Z5nx1jDHyEb4atGVRQ1PNf2apZWVIL2OZw9Q0y9kjrGA/G3+cZFFEWKQfCa eSvjceLyaYX3/fznV6XTy689ebjx1+/8v2u3L9nb2GWrYxjYNnHigbA66pB8Nu3NzxduqeebNpii nfjfr4ABf98+74yEk7EzAhfOkesYMKGv0YFgamJ+i0h4b6c8b73W82F8Ke1rNqfZQbbBr+N1y+ud 05wQx3syv1/yfa/P2yaFMpSrna1AQRXgwwCOfZ4t8r+v2r4M+yvhzIfCJpntqAeIpIL1ZCjkpHCj zjjo8rXuLPQkf0lln0e0w4AXPuZAmf6YXib6323KYs2Tlbp41myCsTLEMlIYl+iSJ2B2GAOnlOp9 rqPk9FvxvnlufUXj73edecvKbvq/XjFVUjh9Hlt7yULltK1OJvY9sP/vs+YOnkewM/PQGwPJy73+ 6HwanNc1/tjUGWbo16fPM7KkdnVDQaH/j1b48bM919AVMIN23PdbEjeLY7WtanDo9dHZtvTHO/Rm jqq7MNcP9OqmXjhuxu5bmvebYEaWy2XG+zt1d1XYQFullTeH89k6ujnph+CKkfQjyu7HtHlhkqXH N36bDlBjsMVi2D4zkBU9d4Ra7+776B72KRWgS2aGPVfhtNIukDsLaBsRqa27EhIPftx+LygMbOYo jB/3lGHNwfu97zH26qI970bDsc0o/bx5tOt5yhbDkhR5fherAm/FL2/u6VaCiFNw8Nvc30nSxz/Q IR9Cx9P4O7yeEO9aBMhOzM0YMGD+fY0as0tiq5W6zV8UDSG/+AbiuXPQneO8naiJsHIagGHXN2cS GdhTMKyCzMYvLKGZiaQ8LA2oKHBZRYCgWg6Zhgeod2JlV9i9SzQ6q3GQj84nX5V7r8Z/GqVWt+mk U3xVJOsS1WbOE65yVSChVuK6pAOIN9S/ppKBdoy1OY3GoDdhp3hQaXjO/zNlweNHjhK+VNNWvyIm LJgR6dHSLXN0mKys64jCenj7Xi7/mePIOyF6vDc+9NRnG0KiooQUUqysrBWJpajZbvgADtoHNwAA GqrQ74cmJSYmJyQ3VnoDeHgrmZGfuXL1yB3fGSM8X1PE1weBSXGCpW1KEOnl5ZA9TPwl32ULVt1k lSkkCCwaMeR7vLzePL3FZ6ofWXi7X5txIJxe2tJrRmTqwzaRVgS0ZP3tTatUtiYQyRHGI2wrBLIK GSCUvyVVrFLWbUDRRoLMDKkIWUwK7SeUIL3sZaVWEAxatKN9zRg2N6JUb3pMbmsG5i8xQTzaDLov miGFlKOCOKJMESCAUaHhIQtDBnnVXFmYJSJwtSc+pAChVbvuXK1zpQM5+BcmxjE3rCkunQVIEMYh EKq2HOxCGcjNItHF86EFPiK5hOugEaxGYrBpGJMpuciSAmDKxNRF0iqEUpT0wcmMhLVpYeFN6FWd f1Yl55xCLS8rLiwwGKIPkeQQjYIqUR06uFjURcYImyCWwu2UEpFkSRCyIlpGkoj+fXfdxW+frnT4 PUk5Dq6Cq86WDipILi1hT2iyI6JRaAUMHgpQjMhWOdyuRggBcHzyC56omdy1mS1EabSbSH/Ex8lH lOSwyFsG1eSnL5pwhbNCFwYFCsuFagPorEGu2AkKowlEpUKLEBb7tqtO7l2bbj1a6BLS4zTSogOD 0GbtMbxYJOx01OM+Mvgsrdmuu2puhHIqCEaJBRefVSzlefKO3nG5xl0CUDXKg9RaTeJMzJDJLV6n V516GkYaLxkGhqVNJkjC7KqohgYM3FFjaqVeQpqo425ExdPpEQNZfZ2adkxKum2OAxiAw8h3ZudX HYhE8GVyTFzySCxJ3mpoXCQ5sSCgxNBpLD1IW9zeUCETgUlxWaSnmWJr2HnKBS8R1nnrCTzrWgKh NbW63ms87yy8zeYvMtT4C8cwZxAe9GVERSYIgQyWSUkYkHQGBofvjGdTDEhKoUQVE2Nb5bzFJMtS VlExssKCuoDADguVgaGIehYi81bo8q4XBUn2PRNSmsEGnCm84qWRIhEEDYvNpsDDKtVODuPoWulu I1xDEUDJhnFRSiGAPsXE9GAd47cdSfit5nC32kaeOoiAdSJI6k467mDY4tzzMATcaNHYoTMjKEHq kw9clHaPAhD14gJLYPZqbCRocnQ7a8hY/AuhUtYqcn1Lksbmw0lheZyh1vUPc9L6XcBi2gVHK62x A6ne9E8BPnlnSTr6knOx6yBnHQhl7WrGN7WYdaVTnm1gkTCWVZVObTV4hnsqyqNEOlnc4C3Wml7K zfp6owmqDWpI0Iyl5QSuhjLQvVTYkyi4gcBpcTUkmCgKF1CZ89Tk3xpntk30ZAJIyAvjXOmxiM09 RkvV05EIilxFonBPBMOtQ3LgeJvRlvWPdPTKOd5lGG3aeaezMwamXJGL8aakwl52A20KZN2m9IYS 0+pyVuZrk2NDRIOpqblDkGcSYiTkicrJsqL1UpkB0ratSA715VxUy4LrWYtCpUFhljtNW9zOnXBF A29oMSeyNG9ieEXdLQssqiGe0012g0Z0MWNMbW85aWyD0smQeaGCw9ch3Qjv9RY2xxBooYhFI4sS fY9ZBY2ZJJoJiYhGYxBR7ZusOhFfbQOgw05LjKb1KKTcqvkodTvW++QKpeCCDYzLETbArBd2iHnu ajLXHdW1Hgc/IZbybBbJ5rGw0uToU4t9b59cylrD2M+/dRcd33luSyX2zwbYxMbT7VUEyhbiXmsh iXNTQms720rpOzs4FpgYHegc5zrnWlc66xUq9ULRfE82jbkxNucn4uQhC2l9lENijNIWMjpWm8My VNkQQypKTbCJpgECCk4IWnLJdWzpWqvKlJxAdp2mWW8ZLJZC01iRMaYHgzM0z2g2aLpmNMCEZEXE VorF2fMtr5V95ggu9iXe5hx4EAIgRATRGaxrcICyetxIscTYsFRBnH9nO8XQinhw6NR51rOqwuKK 4xHNek3KEc5EsVTpZ9YOh59xN+HGWmdaaDIZoqDjvInv6RSyEPMtQkvJ5jNy0CdPndTu1wU3EjdS ipJg0Ow1FpeZ661gUkxiaS00E5oxNJI6xUCOdTHYt62dBUQIhzKCYsAadCwY2BjhSKrvw31dkht6 Mcy7uMusVQvFbc3VO2NU6lnFYYsRqMDdyYw1cPfgczY6pnxd7lyChLUkCAmByyLlJNEAQtgQOeXM d4J9TwNuBZG6/YC2qT4cBUgPgcOA0a8pJegtB24tDMjCZGFM0HQe2Sl0KZMtyIwuOIu22k8ysDJJ xYi15I5pIZ5YK5Gm6EZ5lA8jq+07jRdxVb05SExwKTksSASIltqMFJdRoVElcpIDlBcCrXiPLsNn 6o577NeGdjaGlRfnRmx8rrdrU8OwszMiudCWeUfOSIDceCYkIgKvkJe4IhvEsWotBZTnEbKpyTYR 2mhrJiOZfJ5vZW2OL92hsu9AUvxtuUGaCI4K6bU5huLGgzAzDGNWMZtklFF8TQfQg3JPUWVsuuLN P09IPWmabZ4k7DFE4Tk+vwKy7RjQYGtyAymJGkmxBFF1+jV0cJcePly5ts+6FEr7rkI0STjl5uKz eROORI4mw0IlMQOPGspKSsqKjlcXk1ZG4iUmtYqSyoWS8K5K1SOZbFyzonMxsZKyFxPg0cNZeWqP ZdzleKhUhUWYZEM7OczaZYIvsP0JhgqACGaYo+nTv6hQctsaa5xkNqoodocXq6e+LAcUE0UaSLh9 Bvhg1+4Sm2AOgjUPYX41a0DO4GKFwQoWDaXF1FglIPSjLIOuQYZBzYLrUhBd1LI6BW3PMrD7g7Yt 32hbYM5xLJ6ZlTM7DMpuXNSyzKHJguage5YXZnR58S+kSyq2vDfq/FXDrayuDWXSJaZiYhrq6wsA gKIy5UVlbE0DAwqABw4kTJs4YzOCAMkiWVw7ZUkay6HBdaO+YhGYEE+QDOpts7umw4OpkQSOkLNM O6qEcnosubmWHkzgtNOM6XmqEXw/AbwYuKw9kThnqGiTodBj4Dns5JAuLgQMDh2iL906uNCm0lpY 7FxIY39Ov91s3L2y9jdbwdP4YQmv0uhUfBlvv1S3kCdDUScSNUxtfOG8mZ1FnqBFUpEZzRCJBFiw 5QioIjFK0wMvfb5QyqCL4W+W6urMpWG297qkUUlkRAng+d9H6Eemfl/T9hgeYR8w94+CV71Lf0lQ 9BWRi1nxIcZ0GAOfDceIjzyHeqdyYyIHo979HjyQig5MikHBtohBjQyxuTJxZgEjilbykHqdvmzO ZjhL3tT9XGBExBB8BgHUdJklJWW7QfUCOzeh+5h4mgb0itP2qAPnxdShHXXV+UYDzkHSSUIuTFNC PWw9cY6Nj3PY36eB2hmUZJG/u02uGuG46QxwYoQKec/dWooaGxpUQY7JAzTFoVZRBVjABJx9Mg05 veH0gyLZqIMIfVQCwAPVFVCwDGBAAICfmEOwP0n5399o/D+tEuv6xf/FX2eJNkOcwpAQiyP+iB7k kJt0Dgt01v2HAsnxQBQNQAfF5c1LbPLrHK2uH3pdZvBE+lB/H5P62Ax+USPqRlTME5QgBv4inQig iJXoAbUmBWG7/9y7PVD7KakkYJaUARtcJlNhU6Ac1nCffnOKuQcdz9v9xSkJJADQL8RDO61TtnAB 3Hfx4x3ko67exwsVEWfMz5zbKZQFFkibflMBjsDs83GBNn/sKskmDY70gYigDrXBVcduwTUIpYDp 4Pqwly5oJdNk6z95wCCWI5w8OvMO3x3Im5gBqcDNSjxrC1XZBTwmhE+NvNsoBH0x3oleATlhlOoA KaCdjKTxcxFkiXCwTIMvFwIYNpmVQ5hxqMhj5uEGlkDyKhhZ04qtvXf5Z1Zihxy41PjNiplVOfd6 tXMgFgtEZgY46xrfBS5V2YcA0H4D6fwUEyfjEu1d9P1diJ4v4KufpMgAcMgAXUdItyrkLEskA1BS kYfyqBpFPsH2UA+1Nq/RrfzJ7Sw+iASAVEuyF9ifa3DxNwUChLVQ3hqL7rFHIUJd2Sbbwu4NdkB+ CaIYJghHAuit1uNC2rkZQ0ARJExJYkLTztFHTRoHz5wSllEuUILm/XmM43EwrmDIZBT9ZVJGhZtu GjguTYLgo2RvmGaSCoUQxpWUKqRszoIuGiLFjMM8kUKZKQR0s9+owa9EkBB1DJon3N+hwa7BhYRR GDFEiBkCwIpeM0JsAyshlo0cS2RzQaFwDMi5zsIsgpWBoYwio5yCJYx1vUY4uL92Tp+zbRJP3RLi NbPu48z6J9Z95+oLrtACIn1kBi8uLz9J+oYnKiY/Waaj9ZE4ISPzKw+xan8AsuBnyGhyetamDg9f cSWOm5roXRkMVWkAdHPJQFHl5rsTOWHsQWF/hyaIjww5IBiABR9YzMTgZmY4lvI6DHicDA2ne5Nx JipRpPyzeAGoUjTdpj2QO7h9wd+9z3nL2n7YwplfvutMMx0moQkyhcVpDTkb25XKqplOe68IyqFt UffBpxon2n4QSKFPuwDPoXJQI7w5RJah2wEuOJQJrtvc8P33IJRyns7h22h9bGQE3cZlQ5oABlDT Ged8eWMTXCiUn9FpItiAz94jXfNH4CARMlMs/SUuoGYrNtmJPMC5VuXgDUd91Uy2742IELsJiRtI RjFXVRJkmLVLSDnOafIQy4YVMBA1Uxa5zqf2PNotRieh+jAQaWSXMXG48x1ZTGJP5g1ryk8/pJER yY7jvVZ3Fo5MdpBOgHIqY8BcQIjlwkg8R3FBgOa+IdKd7gghtBWXLzlJabS8QjAxKJzSUItSiaT0 rQdh21E5eMl/z1rpA1lhmZbF2Knj3NyeCe1DSD7PYdZ91tJP0eR4g8skATyxENpAEah7nrdxaljl /xL7TxOvN6XBC6CGboet73Mt55b6dQoj/uaiWW9ql1qdHiCxX5CCGmmPd3njPNeeU0Ig+Az3M2qd hsX6YsaFi6ki5qWKdvRKG/l/H07SCfwdCtOwittQ4l17UKFl4wxkdC/aCRsMCYcqpvFW5w3nk6uM 4fJIyRYQzHVzCdzcaz8bZu8lk0yOjSErnieJBfRdxBhwxR8/iVGMDm96PzN+ie0jhp4gneIlgmer fW7nEoABneImM8jct4XtnMgBgUyHVQ9Eu1lD98/7z+qrwjemiXnPkuJX0rw6h5YZCO5EjPYr6Bug 1avBKLwGePbXWdsQgwwqLBUlS0GZ81syhE+RBCTTgMkO3DIFES0CEZIcqX6DCEqFFHH+aZHFQ1iL NEmcUc0lYGE4IgSexIFcRpkaSJ1nixzKDoPAWrQOeHn/ESVysJoeU7NJBRKjxExEFMMYIR7KeBct jAGSEVFgxR2mg1GrimiOuJQBMaxRZEckCwi8YaTMU5k9mRDznF71scBkhiYOoyFxWhIaQ9sc/S7r uxJiLMmXDg4Q7SQ7AKo+ZbB95qUCqqS9a3HyVu08IfiDeI8BJQlw3r7ST9r/fs1f7ROSqOqvRC2Y /Z+pw1NYxQajcdZ8xGw6QRrOKAoWZVQfkUdawnznpE1o47VsgAm2GMrTFIIhe9fQq3VyuAL+Iv/e WIfqZ9eNeFLve+4PsDmpJA9335rqoUA+sNjYkumYeEssJHS1WKAVeemjClXSjRiBgwMXEAIA9fnu IzxzBjZxqndoEiDSijdSxym8oJI6VYy+WIGgRYC4wNywRAUj32LDVZx5o22VreMi2rZmb4x8zjQ4 WZZuIY1+y0hZs5tu5ym8KVKlTMzAAHdREJBmrB6j1rqbb9YA9BnoI8D6SBQTg+yZH3yTQkrUcVI7 dDPZQDF73PCuamhlJc+knYdRybWLl/q5WpYMFzqvNili8XGocahXWZ4JrgFLOcsraqGQU4wjQLCq uEqkENhxl54OXjDCJtEr6Wu5xQzRrdUG4G93fuOY2xJsBjjy13qJalo0NSjnMrytdcChpN2QdkYQ KnFVG9qUCZE9LKJhjk2t4wt5QIEOSRIUyILcrwCLrc97Y3k1HIWG88H4snSeAmOe0HVbb4NpMcwq 0lprQHgO8gSUBhTCnHq3m1NbFABGGfMToM762bqeKClfJJkUMYbfCqA2ovFwcADooCoZNcivyHYY HNE6x1Re7ITwYUBCPkwICUmHcWJ1kyLEmAsaowD0e16Ta9ppgQyUWhJKRAiDTi8oabi5xMIxWAqg qwQPEW+K/YZiRmE7XEWKXtfJjSKllm8Q3arJKD3AzTOEIv9s7+naqr5ZFC6+1D17CdtxbvQwIze7 ngBmTusupKxcJUhT4+BIO9ZSGXCEAQGXuFmtqfEwPu6mQK0nDpavfFhGNyGwdeFiDTycWOl7hmZm MY5XxWmeTJJynYvUkyizMwQ20mxGIIQ7ODSAUIjIEYkCIF11q9FmStyJQFcqOupR04UIlzlhoEos SRWo1MBvfU3UsAfBDcY++pSLEskiQepgnv+qwtFOAH+HK03pm1GxyOLBpdICRUKHJdtd2HggiQOe EFKhRIVAWG/v5wd8hD1RMGmkT2jBvNxgek7TtGKFEc6iJOUIC9RIFtZI7jQLf7HNJcTED0kS7gqy RBXmBMUGkZFJEKDZvXqKy9ZECs1mgEUNUSLBRVhQWBkaKgRDX2LsQEc8DL+VQ4fSoQEqiaE3MoHj diBk5nqULR7YechPQ+mo4sJmo8jlETsex2tr8Jsjnmw4YHEZrUxcz3F+wjAU+aL1w9ZsLakTz8Jw 7NT2KpWdT0pAVAwAYd4YZVN1rvVR6CgCtkAq3mWqCrrZHqYyRk6Nm6Q7Tavhgupck+hXi5lmXYMr ixgrYxMiBjt8wFOJlsdyhzxcvBh7nP4UBIfV9xW+0L0GMlktxFMxSSMgQVmRoC57nQB5X6m19L6j HwNXHFALilIMUTZWv97EWoc8Nwge+5O2csFB3MRLIBIsjBZIisWLAFNXobZuZ0kTTQTafgpXJ0NS 8hWzlPCypVyZ9j6rEPlY7hMjzvkczedwqJicIEmbvtxQIlgnooFIqFjALQAIktND+RuMkH9YlqXi bmg6Kmw3Q2tELTOj43nMnZhz+bsA0wqytODB98FrAEzmq8pK6O/1Xgd849sOzv9fgN4uBiI4axRM WJSqZlESM5xz3QEIyM1LrWvEAKTaxNuXzL3rNduO/PFClKKLZ6SxOWXQDFsjhE2/D4VQ2FqAHG6n UgPEE694xdQJE/IhyYbpScveHXJFHgf3TJF2A6bhpu8gMeHmHSI6IfNGN+JEaX9CWMWpO62TZRxr KuAR1GsMuWUKVr5wETo5dJOQZBy8chqkz9wpaFmc/D6M1odDar2xCRJIoSRXi/CU5ePG+5Z0Vo+T H8mBctvQwdC57VwqEig+plBJH7QTEI8nblPlE6GYjoVsdgiGJdxcO35a6oktZjXe/lsrfRUodbla PudNzPI4vK+Nwbn2NrzvLsrm9O2JbG0hi7XlO5uDZflfW1pl0IFARcvsc4gHmJF6lFf7S4VOhTwh U5TzeXm1LKOT8hRoVg9qJbIkQPM0DkceiImV2OFrc98wVwCjlIlAapASsGiRIkzib/EukvmH36EC LAkCwXMWUlvfNr9AHLuIG2+qhVCaZRAFJxgAWQKBGSAgoWeQQ3HI8KxvQkQUDJoPLxBmZVoqxALW wS+xeWBCDc7radHLgzhAqoy0sbJ/hoIAqfCh2sEmD6NYRZA3l0D17AIg+Mr6YhRKArlk4wF1NW+z EJR2PaB1tto+F8jiYs426g8D4zMOl4b2F613rJnEOPIJhL3L767t88xMGdhBCTCWgAh7E4eNohEW w2htf5zzOeOQ6YkDPOBWGVUlBWndhdiXwTyRy8tpgg4QXuRKFW/GvB5FoChC8qorsigqp8q1LmQE 6tVyigKja3crHBtAU+b31AsENPSk8JVB3gmvRJ6mxBDUgRzWvBMtJsSZIuu+EOEvw7n/CSOTH5xk pVM35pTyNkBkPdiOQ6F5BYKmQXrWH6IZGxWMtew9zxIszMmFHfKYDJgs/skAy2iQUyFdBhwVRVKK qZIpRDJYrrde1FK1gsTKQQgAggIgjDLcawjalVCqodqNUL1D0MCpnsgEQ+sUS0nNNGlbJEsgmErO KUCgkT88KfwWAAmRq5ZQ00tZUN5RDEiTxc82igqLxSNGBkhNGQUhFIsm2sQ2nnzNmqzu6quWHxnn ZqIAmDl/tgFhe59pKJA4wlEDzzwT150QDfwCWkLIxUhzCgJGO5shPUZAsSZglkClkzTAYgsJAEen cvgDkdy5L14Pe2cTyPnex9tde+tUyKEivpoUyNKrQFqBSCRqfFStV3ICuQr1Shq7AYMHxVC9CAx4 BabCwKA+LXS0ehaKv2MkH5GEjee7RpVT6YDCK/O8QHzqUFOlurNJu4gseTYJLAAJtaN8TkENUX3T lirYFhonVw69iFiF4N6PU2firBkgymkNvLTnZTAxpuFkI15JS2wNlRqxavaABQU40EM7igQNqcOn R5VZBEYCxgREkgxgZ2wzs3IcSKBZ5ZyB9hWh/izLtmFAV4paV4Lr0XBwbSK79SH0xzHDMoUJcQ2a YH2WJgfU9+fno7SGVGTCy2w67aQI7hEppRX4PtcjrMKbm4TBUyKN71CJtHziznV0fNVQiJPRaUYE pQWjUEucB/ytEygn4AzRpu9CBzOdw+Zu4ByCBad0Zml0M63qYF8WPayigQSReQ+DlpACx5RE9g50 mxyoF/tR48LLb5PFfX4k9u7Qnw1OyZoH5DJ6nJqbup5OHifW8AqwTu70D3PQ972IJ3TFydDzNWjz NqIHOxxec73M6UDtbA3HOFHY/GQhzTB23rZzIYJGc9tGSgiqZTAzDUlbGA2qrEBSMRiewCSNuGVp FcCocqZZNYCxKwNCTQl1qRKJ2W0KI1ECoFT00FEuEDk/TpsQQpW5gWSFT1ZouS/FNAQw9FTjKgq4 aGN+33ZUNPs0p+kwpor4mq9CAEeNImMO+DSVSlNIsS4EMJkIgK6/4cIiyqHLWO1js+eDwdjxMONB MUIWnvgCKlo6k16ban0SwYv8PpyPeuCrsW9CEoM8w3Ph4G7+9SCA+33tR/vCrQ8pJP3mxrIv+vJx hQQroZCLFpFr9CwQGYIk3rQDSDwpo9QjaHc7OoPOnpkkycmwPhk+uvZVLWvty2y1cWrbVdxczDOM k84iA+z5eUO0wneujhiFkkuN84dD1zAVCDojqMQwhYCYSEqCUDJiACBSABPxvQp2wfjfk2yce0mZ iYSAUF7NgnFDqecyRoEG5VGxVEAlkA3acAMx4moHziMvsKGMOaQrNOORu4NrNYbPGdSi425p1eZx erkNnCXc/ftnDy2iKA8xajyFIsDY4Zlk33TRz62DT0pcm5hOjzGnMnRqQjl21TjfmHW/B2ZMA0zZ ArRywpVKSQCSTYMOFiLDMgGisIcg4OYuk44gU0ooxnZC6iOiEMH5FTyDox7NBZl2iJtBNxCXofcy /FJcPC3KnE9Z8Ue74aXOGMYbpiA0XTEDOoW5TVnHmdJjW2Is33il5Zx224KHfXPxIngqFulhGAbY 14nwvzjRvc4XaS+swg6od2QO69Nttttttttttttttttttt8RD3PPIQ2QDshpANWJrtltltltltlz Nk5yCSCwJF6iX4RUJOK5szGB1zTOF8xhomjpoUZmlDFFAZM8ZgzDEDwZ8IzRZq6UUawQbiQ5+6sv VR/Fxy4CwQthh3LotRJRCtj3KZOcF6CC5/BSX+OWbaHmaMcoDOnH7Z/N5auDaGGyyI15q3SJxY0X PfKZlDqIyR8laub8JqCdN5kY1JeM9kopNUUVIjgtIyiFbuVj8gnc+p6my8MpI69FgNaxqVWqBIkM WhiES5QsO0pp2UMBQvvW8soRMB43li6+heSR4Sx/RIeqJQjBkNBQ4CBIiZUS7CIDoiqIosRUiiEi wkN3RvRERjo9U2eCqCo1L6rtVVbhuGEcr4LrMmKyMye3t16Shqjrsu+E4oEQHuVBBk5GB0pJYMbY I6c7GTBtaUgRJDlmhfQZ86PmXDRqaXKUFCztLsgHnMEShcHjgHFA2o7VwCSWSYaS7H3AK7LlCibc AkXUJNISEJHCVsqGo0MfIgRYE+RAPIwNx1auubSSY5Dr1oC6fJGaKonPA2tDYiFJW3z+QtlVRxeE FHgPSwpmRUVtN1bi2hrcuEDh4QJxZ0Ttfc4nHqvYZ4gMJI858rtFOF8tMhmNUAKKkw1SSuQ6bkqx HsKLtbiifFlIsEGbLq5r62ZjToBEZ7cgFSQUsIebOIDVVuLvS6rrrhSntapudgeJ4PI7HkfA8G14 IF3a8zsU+y1luMRhZqkUmk2uA3cj7UyGJSbIqV6wVlhSp0eC+5blfWXHi5q6lxSxQs+dOvnV9K+p iwgd+2EC8MbYNMEbskXPTdFDVOiaF40KnAxItFJyejZAylfPX7J19Wq/jdJJmM7clH1PGgevaPQ9 DnO0e1iYvK+Eagag68rMjCaiKUCitFDkaFwAbWPNdZ2yy2nyut53c35y7gkc9cop1NxUhA55md4i ahvRs68pVN47jIwOh6QK6UYrG61UqXOrHS2rNPpDW4u9tRd8FAuaIHO9pKiFY0pKL1w+WziqC+eM jVjcX4lcMxtf2v6Gh4drJ1PJQHY8pdcdr9TV7W/telg0AA8rbObuNSB3sPnp6bmTIkUKw6MWBi0Q RhdAzwAaRqQidSUxdNjUA+vQ5WVHIDQIlAAL3U6e1wfI5767nB0O9HsKLY/qyOgCS4masFqUT3rE tzsW4zWauUhC5uXnVSuAnVy2B8r9DvfW2jiD3L5A730L/Na/Uh256oWhkJC9hgxkzCkZkCRIUUl/ 8XckU4UJAJcU9iA= --===============1394145103==--