@@ -511,6 +511,7 @@ static char * mysql_thread_variables_names[]= {
511511 (char *)" proxy_protocol_networks" ,
512512 (char *)" protocol_compression_level" ,
513513 (char *)" ignore_min_gtid_annotations" ,
514+ (char *)" fast_forward_grace_close_ms" ,
514515 NULL
515516};
516517
@@ -1073,8 +1074,12 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() {
10731074 variables.default_variables [i]=strdup (mysql_tracked_variables[i].default_value );
10741075 }
10751076 variables.default_session_track_gtids =strdup ((char *)MYSQL_DEFAULT_SESSION_TRACK_GTIDS);
1077+ // fast_forward_grace_close_ms: Configurable timeout (in milliseconds) for the "fast forward grace close" feature.
1078+ // This feature prevents data loss in fast forward mode by deferring session closure when the backend
1079+ // connection closes unexpectedly, allowing time for pending client output to drain.
10761080 variables.ping_interval_server_msec =10000 ;
10771081 variables.ping_timeout_server =200 ;
1082+ variables.fast_forward_grace_close_ms =5000 ;
10781083 variables.default_schema =strdup ((char *)" information_schema" );
10791084 variables.handle_unknown_charset =1 ;
10801085 variables.interfaces =strdup ((char *)" " );
@@ -2283,6 +2288,7 @@ char ** MySQL_Threads_Handler::get_variables_list() {
22832288 VariablesPointers_int[" handle_unknown_charset" ] = make_tuple (&variables.handle_unknown_charset , 0 , HANDLE_UNKNOWN_CHARSET__MAX_HANDLE_VALUE, false );
22842289 VariablesPointers_int[" ping_interval_server_msec" ] = make_tuple (&variables.ping_interval_server_msec , 1000 , 7 *24 *3600 *1000 , false );
22852290 VariablesPointers_int[" ping_timeout_server" ] = make_tuple (&variables.ping_timeout_server , 10 , 600 *1000 , false );
2291+ VariablesPointers_int[" fast_forward_grace_close_ms" ] = make_tuple (&variables.fast_forward_grace_close_ms , 0 , 3600 *1000 , false );
22862292 VariablesPointers_int[" client_host_cache_size" ] = make_tuple (&variables.client_host_cache_size , 0 , 1024 *1024 , false );
22872293 VariablesPointers_int[" client_host_error_counts" ] = make_tuple (&variables.client_host_error_counts , 0 , 1024 *1024 , false );
22882294 VariablesPointers_int[" handle_warnings" ] = make_tuple (&variables.handle_warnings , 0 , 1 , false );
@@ -3748,7 +3754,25 @@ bool MySQL_Thread::process_data_on_data_stream(MySQL_Data_Stream *myds, unsigned
37483754 // if this is a backend without fast_forward, do not set unhealthy: it will be handled by client library
37493755 if (myds->sess ->session_fast_forward ) { // if fast forward
37503756 if (myds->myds_type ==MYDS_BACKEND) { // and backend
3751- myds->sess ->set_unhealthy (); // set unhealthy
3757+ // myds->sess->set_unhealthy(); // set unhealthy
3758+ // Fast Forward Grace Close Logic:
3759+ // If the backend closed during fast forward mode, we defer session closure to allow
3760+ // pending client output buffers to drain, preventing data loss.
3761+ // Detect if backend closed during fast forward
3762+ if (myds->sess ->backend_closed_in_fast_forward == false ) {
3763+ myds->sess ->backend_closed_in_fast_forward = true ;
3764+ // cerr << __FILE__ << ":" << __LINE__ << " grace_start_time from " << myds->sess->fast_forward_grace_start_time << " to " << curtime << endl;
3765+ myds->sess ->fast_forward_grace_start_time = curtime;
3766+ }
3767+ if (myds->sess ->backend_closed_in_fast_forward ) {
3768+ if (myds->PSarrayIN ->len == 0 && myds->sess ->client_myds ->PSarrayOUT ->len == 0 && (myds->sess ->client_myds ->queueOUT .head - myds->sess ->client_myds ->queueOUT .tail ) == 0 ) {
3769+ // buffers empty, close
3770+ myds->sess ->set_unhealthy (); // set unhealthy
3771+ } else if (curtime - myds->sess ->fast_forward_grace_start_time > (unsigned long long )mysql_thread___fast_forward_grace_close_ms * 1000 ) {
3772+ // timeout, close
3773+ myds->sess ->set_unhealthy (); // set unhealthy
3774+ }
3775+ }
37523776 }
37533777 }
37543778 }
@@ -3925,9 +3949,17 @@ void MySQL_Thread::ProcessAllSessions_Healthy0(MySQL_Session *sess, unsigned int
39253949 sess->client_myds ->addr .port
39263950 );
39273951 } else {
3952+ string extra_info = " " ;
3953+ if (sess->backend_closed_in_fast_forward == true ) {
3954+ unsigned long long lapse = curtime - sess->fast_forward_grace_start_time ;
3955+ extra_info = " Yes , " + to_string (lapse/1000 ) + " ms ago" ;
3956+ } else {
3957+ extra_info = " No" ;
3958+ }
39283959 proxy_warning (
3929- " Closing 'fast_forward' client connection %s:%d\n " , sess->client_myds ->addr .addr ,
3930- sess->client_myds ->addr .port
3960+ " Closing 'fast_forward' client connection %s:%d . Backend already close: %s\n " ,
3961+ sess->client_myds ->addr .addr , sess->client_myds ->addr .port ,
3962+ extra_info.c_str ()
39313963 );
39323964 }
39333965 }
@@ -4137,6 +4169,7 @@ void MySQL_Thread::refresh_variables() {
41374169 REFRESH_VARIABLE_INT (connect_timeout_server);
41384170 REFRESH_VARIABLE_INT (connect_timeout_server_max);
41394171 REFRESH_VARIABLE_INT (free_connections_pct);
4172+ REFRESH_VARIABLE_INT (fast_forward_grace_close_ms);
41404173#ifdef IDLE_THREADS
41414174 REFRESH_VARIABLE_INT (session_idle_ms);
41424175#endif // IDLE_THREADS
0 commit comments