From: Andrei Elkin Date: November 30 2010 2:02pm Subject: bzr commit into mysql-next-mr.crash-safe branch (andrei.elkin:3221) WL#5569 List-Archive: http://lists.mysql.com/commits/125504 Message-Id: <201011301402.oAUE2NSI015070@mysql1000.dsl.inet.fi> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============1078438420==" --===============1078438420== 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 3221 Andrei Elkin 2010-11-30 wl#5569 MTS Fixing group_relay_log_name change propagation from C to W; Garbage collection in the Partition-to-Worker hash is added with a parameter of how many records in the hash are tolerated w/o checking of the usage counter. Adding C-W synchronization due to: - overall WQ:s data max - hitting the limit of a WQ length Adding Flow Control infrastructure with - level of the hungry Worker forcing Coordinator to distribute eagerly symmetrically a Worker whose load is more than 100 % - hungry level is considered as fed-up. - nap time for C in case all WQ:s lengths are above the level. - a weight param to the base nap as a function of the number of fed-up W:s. TODO: UNTIL to force sequential exec; To fix ROWS_QUERY_LOG_EVENT corner case; to fix commented out // if (!ev) delete ev; after wl#5599 is merged (ev->update_pos() is done). @ sql/log_event.cc changes due to FC and WQ:s data size and WQ-lenght synch-ions; @ sql/mysqld.cc placeholders for few mts user interfaces variables are added. @ sql/mysqld.h mts user interfaces variables are interfaced. @ sql/rpl_rli.cc Correcting a cast that otherwise would not let relay log change be seen by Worker. @ sql/rpl_rli.h A set of user options is reflected by new members of the central RLI. A user var propagates its value into RLI at slave sys startup and can't affect the running slave anymore until the slave is stopped. @ sql/rpl_rli_pdb.cc Garbage collection in the Partition-to-Worker hash. @ sql/rpl_rli_pdb.h Exetending Slave_jobs_queue::waited_overfill. and Slave_worker::wq_overrun_set. Overfill is seen as the queue's property whereas wq_overrun_set is about C-W flow-control. @ sql/rpl_slave.cc Initialization of the mts user option in the central RLI is added. Fixing a cast; Todo about ROWS_QUERY_LOG_EVENT; Comments on UNTIL forcing the sequential exec; @ sql/sys_vars.cc A set of mts related user options is added. modified: sql/log_event.cc sql/mysqld.cc sql/mysqld.h sql/rpl_rli.cc sql/rpl_rli.h sql/rpl_rli_pdb.cc sql/rpl_rli_pdb.h sql/rpl_slave.cc sql/sys_vars.cc === modified file 'sql/log_event.cc' --- a/sql/log_event.cc 2010-11-27 15:36:50 +0000 +++ b/sql/log_event.cc 2010-11-30 14:02:15 +0000 @@ -2411,13 +2411,32 @@ void append_item_to_jobs(slave_job_item Slave_worker *w, Relay_log_info *rli) { THD *thd= rli->info_thd; + DBUG_ASSERT(thd == current_thd); thd_proc_info(thd, "Feeding an event to a worker thread"); mysql_mutex_lock(&rli->pending_jobs_lock); - // todo: modify condition for waiting basing on sizes of worker' queues + // C waits basing on *data* sizes in the queues + while (rli->mts_pending_jobs_size + + ((Log_event*) (job_item->data))->data_written + > rli->mts_pending_jobs_size_max) + { + const char *old_msg; + rli->mts_wqs_oversize= TRUE; + rli->wait_jobs++; + old_msg= thd->enter_cond(&rli->pending_jobs_cond, + &rli->pending_jobs_lock, + "Waiting for Workers to unload queues"); + mysql_cond_wait(&rli->pending_jobs_cond, &rli->pending_jobs_lock); + thd->exit_cond(old_msg); + mysql_mutex_lock(&rli->pending_jobs_lock); + if (thd->killed) + return; + } + rli->mts_pending_jobs_size += ((Log_event*) (job_item->data))->data_written; +#if 0 while (rli->pending_jobs >= rli->slave_pending_jobs_max) { const char *old_msg; @@ -2432,25 +2451,47 @@ void append_item_to_jobs(slave_job_item if (thd->killed) return; } +#endif + rli->stmt_jobs++; rli->pending_jobs++; mysql_mutex_unlock(&rli->pending_jobs_lock); + // sleep while all queue lengths are gt Underrun + // sleep time lasts the longer the further WQ:s shift to Overrun + // Workers report their U,O status + + if (rli->mts_wqs_underrun_w_id != (ulong) -1) + { + // todo: experiment with weight to get a good approximation formula + ulong nap_weight= rli->mts_wqs_overrun + 1; + my_sleep(nap_weight * rli->mts_coordinator_basic_nap); + } + if (!w->info_thd->killed) { int ret; mysql_mutex_lock(&w->jobs_lock); - ret= en_queue(&w->jobs, job_item); - - DBUG_ASSERT(ret >= 0); + // possible WQ overfill + while (!thd->killed && (ret= en_queue(&w->jobs, job_item)) == -1) + { + const char *old_msg; + old_msg= thd->enter_cond(&w->jobs_cond, &w->jobs_lock, + "Waiting for an event from sql thread"); + w->jobs.overfill= TRUE; + w->jobs.waited_overfill++; + mysql_cond_wait(&w->jobs_cond, &w->jobs_lock); + thd->exit_cond(old_msg); + mysql_mutex_lock(&w->jobs_lock); + } w->curr_jobs++; if (w->jobs.len == 1) mysql_cond_signal(&w->jobs_cond); - + mysql_mutex_unlock(&w->jobs_lock); } else @@ -2661,6 +2702,14 @@ int slave_worker_exec_job(Slave_worker * mysql_mutex_lock(&w->jobs_lock); de_queue(&w->jobs, job_item); + + /* possible overfill */ + if (w->jobs.len == w->jobs.s - 1 && w->jobs.overfill == TRUE) + { + w->jobs.overfill= FALSE; + mysql_cond_signal(&w->jobs_cond); + } + /* preserving signatures of existing methods. todo: convert update_pos(w->w_rli) -> update_pos(w) @@ -2669,7 +2718,6 @@ int slave_worker_exec_job(Slave_worker * if (!error) ev->update_pos(w->w_rli); - // delete ev; // after ev->update_pos() event is garbage mysql_mutex_unlock(&w->jobs_lock); @@ -2677,18 +2725,57 @@ int slave_worker_exec_job(Slave_worker * mysql_mutex_lock(&rli->pending_jobs_lock); rli->pending_jobs--; - DBUG_ASSERT(rli->pending_jobs < rli->slave_pending_jobs_max); + rli->mts_pending_jobs_size -= ev->data_written; + DBUG_ASSERT(rli->mts_pending_jobs_size < rli->mts_pending_jobs_size_max); + + // underrun + if ((rli->mts_worker_underrun_level * w->jobs.s) / 100 > w->jobs.len) + { + rli-> mts_wqs_underrun_w_id= w->id; + // todo: + // w->underrun_cnt++; + } else if (rli->mts_wqs_underrun_w_id == w->id) + { + rli->mts_wqs_underrun_w_id= (ulong) -1; + } - /* coordinator can be waiting */ + // overrun exploits the underrun level param + if (((100 - rli->mts_worker_underrun_level) * w->jobs.s) / 100 < w->jobs.len) + { + rli->mts_wqs_overrun++; + w->wq_overrun_set= TRUE; + // todo: + // w->underrun_cnt++; + } + else if (w->wq_overrun_set == TRUE) + { + rli->mts_wqs_overrun--; + w->wq_overrun_set= FALSE; + } + + DBUG_ASSERT(rli->mts_wqs_overrun >= 0); - if (rli->pending_jobs == rli->slave_pending_jobs_max - 1 || - rli->pending_jobs == 0) + /* coordinator can be waiting */ + if (rli->mts_pending_jobs_size < rli->mts_pending_jobs_size_max && + rli->mts_wqs_oversize) // TODO: unit/general test wqs_oversize + { + rli->mts_wqs_oversize= FALSE; mysql_cond_signal(&rli->pending_jobs_cond); + } + + //DBUG_ASSERT(rli->pending_jobs < rli->slave_pending_jobs_max); + // if (rli->pending_jobs == rli->slave_pending_jobs_max - 1 || + // rli->pending_jobs == 0) + // mysql_cond_signal(&rli->pending_jobs_cond); + mysql_mutex_unlock(&rli->pending_jobs_lock); w->stmt_jobs++; err: + // if (!ev) + // delete ev; // after ev->update_pos() event is garbage + DBUG_RETURN(error); } === modified file 'sql/mysqld.cc' --- a/sql/mysqld.cc 2010-11-26 21:08:30 +0000 +++ b/sql/mysqld.cc 2010-11-30 14:02:15 +0000 @@ -464,6 +464,10 @@ ulong slave_parallel_workers; ulong slave_max_pending_jobs; my_bool slave_local_timestamp_opt; my_bool opt_slave_run_query_in_parallel; +ulong opt_mts_partition_hash_soft_max; +ulonglong opt_mts_pending_jobs_size_max; +ulong opt_mts_coordinator_basic_nap; +ulong opt_mts_worker_underrun_level; ulong thread_cache_size=0; ulong binlog_cache_size=0; ulonglong max_binlog_cache_size=0; === modified file 'sql/mysqld.h' --- a/sql/mysqld.h 2010-11-26 21:08:30 +0000 +++ b/sql/mysqld.h 2010-11-30 14:02:15 +0000 @@ -176,6 +176,11 @@ extern ulong slave_parallel_workers; extern ulong slave_max_pending_jobs; extern my_bool slave_local_timestamp_opt; extern my_bool opt_slave_run_query_in_parallel; +extern ulong opt_mts_partition_hash_soft_max; +extern ulonglong opt_mts_pending_jobs_size_max; +extern ulong opt_mts_coordinator_basic_nap; +extern ulong opt_mts_worker_underrun_level; + extern uint max_user_connections; extern ulong what_to_log,flush_time; extern ulong max_prepared_stmt_count, prepared_stmt_count; === modified file 'sql/rpl_rli.cc' --- a/sql/rpl_rli.cc 2010-11-25 09:03:54 +0000 +++ b/sql/rpl_rli.cc 2010-11-30 14:02:15 +0000 @@ -162,9 +162,8 @@ Slave_worker* Relay_log_info::get_curren return this_worker; // can be asserted: !this_worker => C for (i= 0; i< workers.elements; i++) { - Slave_worker* w_i; - // todo: optimaze/replace the loop - get_dynamic(const_cast(&workers), (uchar*) &w_i, i); + Slave_worker* w_i= *(Slave_worker**) + dynamic_array_ptr(const_cast(&workers), i); if (w_i->info_thd == current_thd) { return w_i; === modified file 'sql/rpl_rli.h' --- a/sql/rpl_rli.h 2010-11-26 21:08:30 +0000 +++ b/sql/rpl_rli.h 2010-11-30 14:02:15 +0000 @@ -425,13 +425,19 @@ public: mysql_mutex_t pending_jobs_lock; mysql_cond_t pending_jobs_cond; ulong slave_pending_jobs_max; + ulonglong mts_pending_jobs_size; // actual mem usage by WQ:s + ulonglong mts_pending_jobs_size_max; // the max forcing to wait by C + bool mts_wqs_oversize; // C raises flag to wait some memory's released Slave_worker *last_assigned_worker; // a hint to partitioning func for some events Slave_committed_queue *gaq; DYNAMIC_ARRAY curr_group_assigned_parts; // CGAP DYNAMIC_ARRAY curr_group_da; // deferred array to hold part-info-free events bool curr_group_seen_begin; // current group started with B-event or not bool run_query_in_parallel; // Query's default db not the actual db as part - + volatile ulong mts_wqs_underrun_w_id; // Id of a Worker whose queue is getting empty + volatile long mts_wqs_overrun; // W to incr and decr + long mts_worker_underrun_level; // percent of WQ size at which Worker claims hungry + ulong mts_coordinator_basic_nap; // C sleeps to avoid WQs overrun Slave_worker* get_current_worker() const; Slave_worker* set_this_worker(Slave_worker *w) { return this_worker= w; } Slave_worker* this_worker; // used by w_rli. The cental rli has it as NULL. === modified file 'sql/rpl_rli_pdb.cc' --- a/sql/rpl_rli_pdb.cc 2010-11-27 15:36:50 +0000 +++ b/sql/rpl_rli_pdb.cc 2010-11-30 14:02:15 +0000 @@ -303,6 +303,19 @@ Slave_worker *get_slave_worker(const cha hash map. */ 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)); @@ -310,7 +323,6 @@ Slave_worker *get_slave_worker(const cha Allocate an entry to be inserted and if the operation fails an error is returned. */ - char *db= NULL; if (!(db= (char *) my_malloc((size_t)dblength, MYF(0)))) goto err; if (!(entry= (db_worker *) my_malloc(sizeof(db_worker), MYF(0)))) === modified file 'sql/rpl_rli_pdb.h' --- a/sql/rpl_rli_pdb.h 2010-11-27 15:36:50 +0000 +++ b/sql/rpl_rli_pdb.h 2010-11-30 14:02:15 +0000 @@ -152,6 +152,11 @@ public: class Slave_jobs_queue : public circular_buffer_queue { +public: + + /* C marks with true, W signals back at queue back to available */ + bool overfill; + ulonglong waited_overfill; }; class Slave_worker : public Rpl_info_worker @@ -195,7 +200,7 @@ public: volatile int curr_jobs; // the current assignments ulong usage_partition; // number of different partitions handled by this worker volatile bool relay_log_change_notified; // Coord sets and resets, W can read - + bool wq_overrun_set; // W monitors its queue usage to incr/decr rli->mts_wqs_overrun /* We need to make this a dynamic field. /Alfranio */ === modified file 'sql/rpl_slave.cc' --- a/sql/rpl_slave.cc 2010-11-27 15:36:50 +0000 +++ b/sql/rpl_slave.cc 2010-11-30 14:02:15 +0000 @@ -2607,15 +2607,10 @@ int apply_event_and_update_pos(Log_event if (!ev->when) ev->when= my_time(0); ev->thd = thd; // because up to this point, ev->thd == 0 - /* - mts-II: - exec mode can change dynamicaly e.g SEQUENTIAL default -> PARALLEL - but only when the last group has ended - */ if (!rli->is_in_group() && rli->slave_exec_mode != slave_exec_mode_options) rli->slave_exec_mode= slave_exec_mode_options; - int reason= ev->shall_skip(rli); // TODO: MTS skip handling + int reason= ev->shall_skip(rli); if (reason == Log_event::EVENT_SKIP_COUNT) { sql_slave_skip_counter= --rli->slave_skip_counter; @@ -2623,7 +2618,13 @@ int apply_event_and_update_pos(Log_event } if (reason == Log_event::EVENT_SKIP_NOT) { + /* + MTS-todo: to test neither skipping nor delayed-exec logics + are affected by parallel exec mode. + */ + // Sleeps if needed, and unlocks rli->data_lock. + if (sql_delay_event(ev, thd, rli)) DBUG_RETURN(0); exec_res= ev->apply_event(rli); @@ -2812,11 +2813,17 @@ static int exec_relay_log_event(THD* thd /* This tests if the position of the beginning of the current event hits the UNTIL barrier. + MTS: since master,relay-group coordinates change per checkpoint + at the end of the checkpoint interval UNTIL can be left far behind. + Hence, UNTIL forces the sequential applying. + + TODO: to not let to start with UNTIL whenever @@global.max_slave_workers>0. */ if (rli->until_condition != Relay_log_info::UNTIL_NONE && rli->is_until_satisfied(thd, ev)) { char buf[22]; + sql_print_information("Slave SQL thread stopped because it reached its" " UNTIL position %s", llstr(rli->until_pos(), buf)); /* @@ -2875,6 +2882,13 @@ static int exec_relay_log_event(THD* thd else */ + /* MTS: Observation/todo. + + ROWS_QUERY_LOG_EVENT could be supported easier if + destructing part of handle_rows_query_log_event would be merged + with rli->cleanup_context() and the rest move into + ROWS...::do_apply_event + */ if (thd->variables.binlog_rows_query_log_events) handle_rows_query_log_event(ev, rli); @@ -3517,6 +3531,7 @@ pthread_handler_t handle_slave_worker(vo Slave_worker *w= (Slave_worker *) arg; Relay_log_info* rli= w->c_rli; ulong purge_cnt= 0; + ulonglong purge_size= 0; struct slave_job_item _item, *job_item= &_item; my_thread_init(); @@ -3574,6 +3589,7 @@ pthread_handler_t handle_slave_worker(vo while(de_queue(&w->jobs, job_item)) { purge_cnt++; + purge_size += ((Log_event*) (job_item->data))->data_written; DBUG_ASSERT(job_item->data); delete static_cast(job_item->data); } @@ -3584,6 +3600,7 @@ pthread_handler_t handle_slave_worker(vo mysql_mutex_lock(&rli->pending_jobs_lock); rli->pending_jobs -= purge_cnt; + rli->mts_pending_jobs_size -= purge_size; mysql_mutex_unlock(&rli->pending_jobs_lock); mysql_mutex_lock(&w->jobs_lock); @@ -3645,8 +3662,6 @@ int slave_start_single_worker(Relay_log_ w->usage_partition= 0; w->last_group_done_index= rli->gaq->s; // out of range - // Queue initialization - rli->slave_pending_jobs_max= ::slave_max_pending_jobs; // may change while offline w->jobs.s= rli->slave_pending_jobs_max + 1; my_init_dynamic_array(&w->jobs.Q, sizeof(Slave_job_item), w->jobs.s, 0); // todo: implement increment e.g n * 10; for (k= 0; k < w->jobs.s; k++) @@ -3657,7 +3672,9 @@ int slave_start_single_worker(Relay_log_ w->jobs.e= w->jobs.s; w->jobs.a= 0; w->jobs.len= rli->slave_pending_jobs_max + 1; // to first handshake - + w->jobs.overfill= FALSE; // todo: move into Slave_jobs_queue constructor + w->jobs.waited_overfill= 0; + w->wq_overrun_set= FALSE; set_dynamic(&rli->workers, (uchar*) &w, i); mysql_mutex_init(key_mutex_slave_parallel_worker[i], &w->jobs_lock, MY_MUTEX_INIT_FAST); @@ -3710,6 +3727,15 @@ int slave_start_workers(Relay_log_info * rli->gaq= new Slave_committed_queue(rli->get_group_master_log_name(), sizeof(Slave_job_group), ::slave_max_pending_jobs, n); + + // size of WQ stays fixed in one slave session + rli->slave_pending_jobs_max= ::slave_max_pending_jobs; + rli->mts_pending_jobs_size= 0; + rli->mts_pending_jobs_size_max= ::opt_mts_pending_jobs_size_max; + rli->mts_wqs_underrun_w_id= (ulong) -1; + rli->mts_wqs_overrun= 0; + rli->mts_coordinator_basic_nap= ::opt_mts_coordinator_basic_nap; + rli->mts_worker_underrun_level= ::opt_mts_worker_underrun_level; rli->mts_total_groups= 0; rli->slave_worker_is_error= NULL; rli->curr_group_seen_begin= NULL; @@ -3792,6 +3818,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(); delete rli->gaq; @@ -5355,9 +5382,9 @@ static Log_event* next_event(Relay_log_i } /* Reset the relay-log-change-notified status of Slave Workers */ - for (uint i; i < rli->workers.elements; i++) + for (uint i= 0; i < rli->workers.elements; i++) { - Slave_worker *w= (Slave_worker *) dynamic_array_ptr(&rli->workers, i); + Slave_worker *w= *(Slave_worker **) dynamic_array_ptr(&rli->workers, i); w->relay_log_change_notified= FALSE; } === modified file 'sql/sys_vars.cc' --- a/sql/sys_vars.cc 2010-11-26 21:08:30 +0000 +++ b/sql/sys_vars.cc 2010-11-30 14:02:15 +0000 @@ -3109,6 +3109,9 @@ static Sys_var_ulong Sys_slave_parallel_ "Number of worker threads for executing events in parallel ", GLOBAL_VAR(slave_parallel_workers), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, ULONG_MAX), DEFAULT(4), BLOCK_SIZE(1)); + +// TODO: redefine slave_max_pending_jobs + static Sys_var_ulong Sys_slave_max_pending_jobs( "slave_max_pending_jobs", "Number of replication events read out of Relay log and still not applied. " @@ -3119,7 +3122,7 @@ static Sys_var_ulong Sys_slave_max_pendi static Sys_var_mybool Sys_slave_local_timestamp( "slave_local_timestamp", "if enabled slave computes the event appying " "time value to implicitly affected timestamp columms. Otherwise (default) " - "installs prescribed by the master value.", + "installs prescribed by the master value", GLOBAL_VAR(slave_local_timestamp_opt), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); static Sys_var_mybool Sys_slave_run_query_in_parallel( "slave_run_query_in_parallel", @@ -3127,6 +3130,32 @@ static Sys_var_mybool Sys_slave_run_quer "for parallel execution of Query_log_event ", GLOBAL_VAR(opt_slave_run_query_in_parallel), CMD_LINE(OPT_ARG), DEFAULT(FALSE)); +static Sys_var_ulong Sys_mts_partition_hash_soft_max( + "opt_mts_partition_hash_soft_max", + "Number of records in the mts partition hash below which " + "entries with zero usage are tolerated", + GLOBAL_VAR(opt_mts_partition_hash_soft_max), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, ULONG_MAX), DEFAULT(16), BLOCK_SIZE(1)); +static Sys_var_ulonglong Sys_mts_pending_jobs_size_max( + "opt_mts_pending_jobs_size_max", + "Max size of Slave Worker queues holding yet not applied events." + "The least possible value must be not less than the master size " + "max_allowed_packet.", + GLOBAL_VAR(opt_mts_pending_jobs_size_max), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1024, (ulonglong)~(intptr)0), DEFAULT(16 * 1024*1024), + BLOCK_SIZE(1024), ON_CHECK(0)); +static Sys_var_ulong Sys_mts_coordinator_basic_nap( + "opt_mts_coordinator_basic_nap", + "Time in msec to sleep by MTS Coordinator to avoid the Worker queues " + "room overrun", + GLOBAL_VAR(opt_mts_coordinator_basic_nap), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, ULONG_MAX), DEFAULT(0), BLOCK_SIZE(1)); +static Sys_var_ulong Sys_mts_worker_underrun_level( + "opt_mts_worker_underrun_level", + "percent of Worker queue size at which Worker is considered to become " + "hungry", + GLOBAL_VAR(opt_mts_worker_underrun_level), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(0, 100), DEFAULT(0), BLOCK_SIZE(1)); #endif static bool check_locale(sys_var *self, THD *thd, set_var *var) --===============1078438420== 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: f789729777ddf07deb205281c2611d434068a8e4 # timestamp: 2010-11-30 16:02:23 +0200 # base_revision_id: andrei.elkin@stripped\ # tizvu3c0ovmoj1g5 # # Begin bundle IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWTOkdEwADTF/gH+UACh7//// f+//8L////9gGg6d8vve9vHd8Gu1sPT2+73vPXd9aqgDQybJq2fd87Ay+g7ttle9j1swMvr6pSX0 19HyRE7bbTWttXRlp3bSbZoNNIENCYUeQ0CYqZtCh40ao02Ueo0GmgBoAG1AyEAmjKMmVPTTVGQ0 9NQNHqAAANAANAAAlAIFNSY1NMJ6k0MmaU/VPSep+oAEZBoGQ0A0aYaCQkk0JslPSemURtGk8Jpp oJiaNAZAABkA0DIIpCU8RGEaHpU/TTUbUZPIk/UE9NR6EHqNG1AAABoCRIIAIAmUwICYIRPKHqNP UD1DZTwUD1AB6k4AH0kBAg8uj1eK6Sr1X9n6iebc7mL/td5YIz41vxbqOejdXPR3dRm/NzPDU6aZ U0PWztXJfflnM84fp+uk8/5a+/tuY3GJhbZva0ezARazicEiwBSR+CqP0tE7P4wfTTFzZMk8ry3r VFOUdJIeewBilBnIGt0YcNEJ/JqoijQvidcmTVD7aIaG/Rvjo+u/hvVd//2ceaXQx95xc8zG6cRX Ih2KOHe143+H/u2GqD+RUK+UiXKh7ZVq4vgw7L8LoYx3ycIRvYUaRa1cwVGCrZezYkVlZLGeMiUS a3rsU3k3KhrRQ+6yqtqqsYw+VUMKbkRATd5GPR0SKa+ilFQcG2yURgtIQzwZ4VJQoFNCGNfyVBVs cbSMGhXmRxIyNLawRw4ZRhRg7HsTaIHwBJc6tGcz1QGGoSIrRl55LniFMrdjUJQyef7tbblUZwRz aWLIaPcg0+KC5A222xtJtCbbSGxtLMGDF6EB1zj4vJhm6VcVzz9U6fHu9hTy8Nxuy3EsKV5Yfzwk zUphUwoOw61ESxyKWUS3caI05bDaygzqYUpcKRiR3UwKMpK4ulnJw0qWoGqGeWMq0VmMtYteZdpd rrZbZmZIHusAOQZlfZlc48ehmp8GLpuTct+Mc5j5IAezezkRoNu5HtpdyzyrbdbvHvPTp5Ty4sPI NDFG/IwfD142Tfux7L2Fs6qmK1cj4CIdSL2S445d1caTYKj1bRRRO+vNoXvMC79zs/5+l4sZKEfC jlfWmRhhGUj9aLLgHjOqzD1RBxcFp4QrCkN9nCrQh6Nseh8FFGEnS0PY6/HGcyBkGH6NiGdpBM9u VRXXClEbB2KRqOUXo9u+OZQYCPMbARVV4c1WpwNhrGExRSnTzlCHGEe3WNHKLqfCyxOttTUZqpYN vp4mJkNLlE8mdaNFD8si4UoR2sub1oAy18bVV2vJQyIG4qiJds7iUEeyjJAYl8cNqUp096eT6bXI bYzk8lozLeK0xmqcY7nVtbmbn6MCy2y57gh4Y6EOF5W81WqC1TKJig9QXNTJfkhsyYn5HC05njG3 BtPrnWSy3G9uHQID2rmPAeC7d8nld78uI4b2ohs4e2zVD9Zl9O7HGieTDfLo8Zy9qdhDcdVHbVcy oRA5pcFSaiZtpzZ1YUTC8HRwVmcLSDFlArv0234bLxHCwW0utJarWZmUzulAHWUA86eZQOXp7epV 4b3bO6PExe3EpYASD2IBPMgFz65NnCmp18i+2MWPlddm2IiVeAHz/Q0IjEJKEIURBKO8ZuVCmgIZ FCKDVGnFUJoI/Uytfc1uc1zt8J/lVH3D8fePwFeyEUDtmOVepo1Yn7BVMzGZwbBeXo8O5mbt8S59 4xQOvHMmPoTjHdXS8KDNw6kxVEbfWAwctN/EG/yTCqpLMLegSOK1zcnngqLse6mEqSe9nIB4yKCL 9aFiM9P2wt//MAeJqcfHsxKIqcEbGDDjubWmXKgJnMjC5+iZztDaen6fUOhhPEgoh4lQmmmuEQHj aQKb6HyVnCbThebIHnRIKlQnDMkaZsw7x8I70ydCFHbgmAEZrtOa6ZPR3I6hFS56hJSkyARRIj5m F+dD1s2CVnaT/KVdu54Elj7dPEmBm6v+T5Ui8qVLHiqqTpSlZJAOEgJ0gGoUOJLNhqNHuR8A4BpC 55zttTzRcrKfmYo18UUYxF+/1tSDDd0nC9bI2xid/NweENHrjEK9o+NiXG1kYLkaadkidtKUCwtJ HUz0zJtYd1ffpWliGgkyLzsGKhRTFoNCBiAsCRnEiiGkSN4gRCh4kJSssSAhQzNywnBwkQhUoRAS 7PBSheCCNbEE1cg8jNIwmrdRanuJF/0lqvNS4xUjEsdoRMlIm4L8vbSwDKgb7bOmJYOPkezQPc4m cVNWV3ym1cCFv0PTSjogyXEvDJdDPQ9euFSNwAU4bkJEsHsCQgEHUwzpUR4GpRVIOa59eMa7sbtH wjm5ZmhlbNAsSkStriJHNI/TYJiLfhuw1LgkcAd5MTMXWMNhhvfNHpAYi4awio1zSPF2mXl4E/je dmRrTQx8/Ou+23VBd2FNuxudDRByOxBYanA5OJLA/GTKZw1mt93Q0H24v1gKLKb3EBEJBE6wP01n eET1Ejfwgx30ThbzIhaGG4LQgFUukS8BlYLNv0LQ3zN4rY3qKbYMBqgMNsiAyZW1uKpPIYWXSUhT 2kQYXm854KiFLHv/VSNDA3ZHAyJKF8wmg1Ej1i6LQXJbsdihcEERSUPfPlSUqxD2HVPZbUKiRYXG 1XIMDQkTmmNctyklNyoUUs7zAgPAusr2mMXc81K5jY0mqLzGKxmeD3iRjFpl12ndccAwi7KHmbSQ dAxQdUqrlmcEjPYuiDmHcrlcLan6DgRszJRKWecyIhzqIvCuqkgdUAdqd1QschSGgbcNrht2GJYq 4q11ILsbhLdQ9xSiXeTMk7k78FCj3pY6hW0+cdF8W08xEq81YSJE07i7AvOw2m/cW/qg71C4KbWb Urm5RvbIWgkVzSppoVU6iZWjN19kSLFMsSZzQOCdwsHZed36eqtCycwW/vz1eNiZIyaynJzsIswn 0QW06QJZaVggsGjifQKHuPUvcmNz5ppuEhZ7mvk7FkczOfkC+4dARQTyLmO2/BicERh6wMMKCuhp Hn2upQa1olqlhgJi+IQmxe97DrV5pTnjF1HBLsTnNhit8bJVxSLCezvLl0GMKDJYGw1mC+IbVas9 xY9GSc6StbwLL3hOWqCRyNaljWaVEzpvNxECpwQFIL3l88Uq6pRTYg0cBc0F8CIILhhS1RmQNZOm e8vVtEkWhwuMgofvFhCI6B7R7h6TUxPA2OgJHCwxxxc0tkhBkSLr2fM6Jz0lJom1SYGlcM2CVuHO daPhS+m3NQuqkg2IsuSoNOWNhSS21Fk0XLld1jBycYcRzcOHjEjE4npWpkZrsEjw3U502PaRKcS4 vZlWJoOFJ7RpEyXgS8wbXJTnKmO8wu4pA9XVkCCjWo6N5BOzE563xnYZFQqZmRkcefevv/hghxEO /DGEezkG/zN8MkXKehBoyF267LuIGlEo5wIYEThKRV5IiQw64j4EPpmKELY3ZXOnemtqbUCRuXGV 738vbZexT9S0++JlVRvA3I7H6fO79sMsMQqY9fXavZn2NouRjWVxyejBeeegxSxWGMVaLIVQqqFI 0hHneEFY6v1iOzf9SeY1bXpQ12yc9JN+60lOcdbnFOo8n1naKGNssQUgJsoYiIhWN5Dx9aEr0dvn unQDrgQSX7bagaTN7A/lOkP8+gKB/yFQewpWWb0kiAgSDzBsn7PuDrfKgaegcP4XZMqnOoSH+LBx BcP6J6Qe0PwPrG2M2//bw3BwUBQKrctdrRoD3DsDEOAWhuUitv4/j9K4MPpQwuDQMrbUcUVzX6Bs C7iHUJKRNDBh96kXBLA3PX0fnmFQ4HFYotlZ/r2BtA4A0V3A+TX1CNqxCMgkFcw3BMLF623FoYIa auZgjmGAVCisV3/n5xJB5i4MwgWk+LF5kbyZKqPsXUGFszMGqs6BqG5euhEC5Q2yRMhCxu3G4JBx ZwwQzDOyMrMPzDgDy9vwHrQ8nQSJGwekOqSHmD7j0yQgsH0OxAwiAuQNH1BUJMRIYIYKYjMuFaWj yDbbIyNsjPcFVNDaG0NhoMuKDFVCqX4JNEMX8ojAvUgQzAgxerxBGtaJZoLypNobbkgaRVXXWi0D ISqKxMV4rzAwsY3sB1ENoD7wy7kr/S4YXmSRU/oIQf1hB9jP34noX3fcSIqYAz8JH4EjXsNVOTN/ BvkkBTiY0NV95QJ6G7cUMwqGWJfyBjBZAsPtq/hQPeF0jk8qf1fgagfY429HR618pIwgqkVNmafU oRBT0g9h4hIQl2UDJ6q9tGQSwQVOfkxThcoev3lh9DNrKIRUwwIJb3+oxRVacOTjMFJBDUdMprg1 WPhUfrBJo7vLsgVsFHkeW44siFZdESDM2B1wKUb5G117gm6d8yms5mZqNczvFhQqLCDmpIO4kvxJ lC06hBUggt9wMXAZtUpdyB+jz/jgMmjOTMyXY975rU6p0TPX2qqksdAEuu5swta6MngVGoOFnS4B xm9BQuzHtL85XM8JeLD4y7B78qTTGZ2DBai81VM/IBZ4EC7cO00Ng26TxjUBozwABeKwVEojHBLU nBkRvgojgo6YTpOnWAzzeV8SA+7pFG8kBdMbCUMez5atqkQXoiebemUg2MnGb5iRMox0T1l7BqYw sl+6c51lIOc4kDm4F7xR5IGSaLRHU8D0dHrivEZqefNaHcQd4zUeQwkPVI1A10SLp6GVgCqKxCQy cx4E+hQ84JofpD9fC5zlG8blNhk99JD5jhp9FQskeOdu83IRv+zqS4CPSCxJVYgMHmB6Jd4kWBBe jc1h2VlvNXtqclXdzvPEpxKPjKfewWm0xTHXZxS6sOva6lkCXwpgd8XVVefRDFWZPUWqYqnfRbRM BmmMZw+8mKWbq5izK7vivL1UZvLHKV/K5EAkfE+U+Xptyz9+4yPafEUvmLeZ4IgmWrfhENnBRNfE xIMfgbON3NpLqg4asWTUcRByMQViRCKk5qQsip069tknERqbzgfUa8SDeXqtyPMbcHKC7WHLYWPy 2Yyoy92n1YE7H6HBk2x2B5dC+fDoe75uRO/1dz4m1H5oKGuU+M5Ag9OwNY7UWNLz59Xf3r55VRgj J+39sk0gLfIr5+oPMd1JzvGQq+t+euqIDh13huLYaI2tDQ0E1HIrK45sO/4cuR5kjUDfaDfW+smn qbtui1y3k22dKlV+9LWI0NtnZ1lJjEX4cjEvtLFQFqX1woGBVWDTaGCQlCa1BYiIhKmpqHYPdg7U 2dvWQsCJOiGs6idTLB6MLWpL2WllMEL8jmubLRLugaB8bAT+RuIuqeIgnSFDlDBkANghq1K3yVxz ngO4hz+w+FwuisKHnOC3TPgfA+BmY3GRqCuXkbhGB6UFb6UNlyEIWIYgb8nKxKONwIWuY8RUpbGp Tgt08wtSyhJVQm0Cm0ekZ0S0UgkFEDEA61PmXFZqQVYMaSLUftlJ2GJDLvxkBJuQq0xQIh43uoea bceGMUQbrIJ+QGhSiGhpImxC5uhhMBh6wJCD7WIOyivTlzY3coVzXS/MIiQ/JWWs96OCovn8RCIL qoI8WDBGagQky5nu1FQmc2gjlYxMTCmoohTgYuw3jOa8jMM8JOFS0nflbt82FPd6pBEHuUExbwXL 51/ir/TVckEhgIoeiXk9Ga252NA0gMgWLBFoCRy2OhnIjSUPRLFuUwEN8AFCADRhvBnAHWPl9ezP JA2NtAxNmzU7LhLv4+oFrHk1w4kI8AsSupMxQRF+19Qv6WdF+Ror/NuSEN1SqYJdVMU3GnK7jgyu 6mlGCIIIIIiEHI7rqbnQCy1WiqgxEgp61gbQQYgp3DOSsxVZbnBuTFZR3Z3V3z1zPSLIpUiEu8Qg OE5BNkqBelwxttqovIEQzxwjnpytzH/ZkgY0NDgLlgI8agYHyDSG270gGkLwi3DHMMMUTlQ9q6TW Jie6SqnzYwbRquYICu6nI+61IDcpDbONimghFqd/X58QKfLqXcq2OQmYjIxQYxpyAcmO3Z2Axliy lLXLcS3C7dTsQQDXaU0f7HrhF64RsGxBvZ12mF+NqiEFqlPyHI8BE6UiBx2rcgyVx6nNBd8iTJ4Q O/ARagl4LrlZqKlFe6XYakgmlGQbbUhBsJBNnGwjHmdjsi9NZL5DwN7M64I3kmw0tax6dTtVpZ2q rQTqDRTNIWq4hcjiVK6xYRkSAhEmIm0LxWzRBvGQg6RahWUwci2SJu4RNCYflQEUBBT62kBSrLca kYLZVVTBgxMGmA2wbYNJBYodYAuCFyVfRP+epBejVB76wQXAaHuRtNNi5hgGVlBaKqUTadEQGXCy Z4l1g721o7bR4GkYMHV3cXEguskw1ngCIAWAMNVtS24BDhGbWetheU1krVvXMdDvP5j0+n4GJ0Lb nAIM3977NgxKbAJCUt/U1CA1Zr1elme/xq6rDQTyphjy6+XSU6ZJVFTlOApxJpiSnWoEAyTIDfTE dL2iQkQiYwoE9LPMnYyDmNfsWIZY4Xt7Ce4wG4Xz+1RxOMYwDyJdPFrLuJwPh5js80kKhN+FsOAB bg9qtqiozTTIgOrQNIZIUIKECLxHGP2JsTSRU7sbj2oLEbRIwijPqNsF5ertuiWEjChU34st0tqp eOKzq5rTA770hldCHMKdxeRfXsDtBBmu1b0FggotujaGMN68uCpybGR05C1qVNtCg1TVDRtTs0aG FzC5AyyTEQkkW1JEw8p6zPMUVkhoRAmx9eE7DyZFcyZvghD9uOReRI3o5tejRQS8rmcLItIyZDQ6 6Qd1axIL8eDQo000Ng4TTCipPS/GKynEYgjC5eK1QRINY/jQSNV0Onqv5B8SIl9C/giFI360jcke ww0AIbRBpON41SCnMA30kG2SyKsdBjl329cZnd31UY+Ibqt+7CJlw4r/FHHA3iyN0kteh8fUuvS0 q0vWm662y3Tdgc7FleY3ldzeNslIF7xfIo203VmKSBoNQ+AKQheyZDBEhTFJIXYLaI66nvEjbyN0 PjHY7EXC351n4DGju763CuBQl4NVQfG3VVQch0JkTwBcZ3ANEg2MYInCJHRqtaR0oFYJzDBVSbxW tUKsegg1PLJJJJJJbMg2UgYBgkbybXDeSLxsgi09wMmnaC30rQpTuXFSDLOw8cbsseJvW7muJTx5 CllIHcnqczqmqGqZfx2xNpIuWdp4wJ1IRLoHnX1qyrYX3ysLUDwFvBGAgtVgiwRsku9UoaGmElgB 3Bbi8CHWbRcDsOJtk5CHFyN/gga0Kk59Xq56CuOvLO06UL1Nx2phfULzqm9u3klyqjbUojFRlQV1 0ums+cYxcDBTH5B4QGyfepHTPBIgFngGaZLzQEKyFR6ZRSUmJklAxQoyUmm1FtUTtyt6rxvUvG8a HRcrNm65YncxuY4ZJ/IDBoVDb1Vnh2Lkd76tt2qCbkyXP88kkFtD2FoB3BIpjG3tk74tO0pvYvxX VYJjsdMjgQ1DmeEco0kZ8d4N5YIJWNC+QwQIXNYuiAcjBUHbAkQCcrx42oHYrks5ic6VFw6O91Iu q+VpuwmWdi7Iq2i2Lx1xExnZ8vsnD71fWQ+5QjFhRdYX72rn32oRsxImSiqJM1lAt/Y775BTUKUZ vGQDU0FJIb5DAU7Mr0tlCozNTpfbCh3xhBk+Wt7r0XZuRjqHQorjl1wXMuTfXJSj223Rb+bkIZH8 gDMqAqfC6h4Q2CkmzGb7ABcEKPOxiIvPDyPE5Wpbm1sA7xq/2bOCInD+v/i7kinChIGdI6Jg --===============1078438420==--