@@ -886,7 +886,9 @@ func (mgtr *Migrator) MoveTables() (err error) {
886886 return err
887887 }
888888
889- //TODO: cutover here
889+ if err := mgtr .moveTablesCutOver (); err != nil {
890+ return err
891+ }
890892
891893 if err := mgtr .finalCleanup (); err != nil {
892894 return nil
@@ -904,6 +906,83 @@ func (mgtr *Migrator) MoveTables() (err error) {
904906 return nil
905907}
906908
909+ // moveTablesCutOver orchestrates the cooperative cutover protocol for move-tables
910+ // mode. It implements the T0-T6 transitions described in
911+ // docs/learning/design-refs/coop_cutover.md §1.3.
912+ //
913+ // NOT the standard cutOver() path: every internal call from cutOver() (throttle,
914+ // atomicCutOver, waitForEventsUpToLock, heartbeat-lag) was built on a
915+ // single-server assumption that no longer holds when the applier writes target
916+ // and the streamer reads source. Each is replaced or dropped here.
917+ //
918+ // This commit (1 of 3) lays out the protocol as labeled comment blocks only.
919+ // The protocol logic (RENAME, GTID capture, drain poll, flag set, hooks) lands
920+ // in commit 2. Tests land in commit 3.
921+ func (mgtr * Migrator ) moveTablesCutOver () (err error ) {
922+ if mgtr .migrationContext .Noop {
923+ mgtr .migrationContext .Log .Debugf ("Noop operation; not really moving tables" )
924+ return nil
925+ }
926+
927+ // ----- Postpone gate (precedes T0) -----
928+ // Reuses sleepWhileTrue with a predicate that KEEPS the postpone-flag-file
929+ // branch from standard cutOver() (migrator.go:965-984) and DROPS the
930+ // heartbeat-lag branch (lines 958-964). The heartbeat-lag check is dropped
931+ // because move-tables mode disables _ghc writes (#8206), so
932+ // TimeSinceLastHeartbeatOnChangelog() returns ~58 years (time.Since(zero))
933+ // and would deadlock the gate forever.
934+ // TODO(#8209 commit 2): implement postpone gate (flag-file + unpostpone
935+ // socket command, with OnBeginPostponed firing once).
936+
937+ // ----- T0: on-before-cut-over hook -----
938+ // Fires gh-ost-on-before-cut-over* via mgtr.hooksExecutor.OnBeforeCutOver().
939+ // Non-zero hook exit aborts cutover BEFORE any source DDL.
940+ // TODO(#8209 commit 2): implement T0 hook call with error propagation.
941+
942+ // ----- T1: RENAME source table -----
943+ // Connection: mgtr.inspector.db (privileged source connection per
944+ // move_table_mode.md §1.5; same connection as T2 so the RENAME's own GTID
945+ // is visible in @@gtid_executed when T2 reads it).
946+ // Exec: sqlutils.ExecNoPrepare with no retry — RENAME is not idempotent;
947+ // a partial success leaves the table already renamed and a retry would
948+ // fail. On error: return wrapped err; operator re-runs the whole hook.
949+ // SQL shape: RENAME TABLE `<source_db>`.`<table>` TO `<source_db>`.`_<table>_del`
950+ // TODO(#8209 commit 2): implement RENAME via inspector.db.
951+
952+ // ----- T2: capture @@gtid_executed -----
953+ // Same connection as T1. Parse the result into a *mysql.GTIDBinlogCoordinates
954+ // (the drain GTID — a closed superset of every committed source write to
955+ // the migrated table, per coop_cutover.md §3.2 steps 1-3).
956+ // TODO(#8209 commit 2): implement GTID capture on the inspector connection.
957+
958+ // ----- T3: drain poll -----
959+ // Loop ~100ms ticks (per move_table_mode.md §1.5) until
960+ // applier.CurrentCoordinates contains the drain GTID, comparing via
961+ // GTIDBinlogCoordinates.SmallerThan (go/mysql/binlog_gtid.go). Reads of
962+ // CurrentCoordinates MUST hold CurrentCoordinatesMutex (applier.go:75).
963+ // Timeout after CutOverLockTimeoutSeconds; on timeout return an error
964+ // naming the drain.
965+ // TODO(#8209 commit 2): implement drain poll with mutex-protected reads.
966+
967+ // ----- T4: set CutOverCompleteFlag -----
968+ // atomic.StoreInt64(&mgtr.migrationContext.CutOverCompleteFlag, 1).
969+ // MUST be set BEFORE T5 so the streamer's StreamEvents(canStopStreaming)
970+ // loop can wind down in parallel with the (potentially slow) on-success
971+ // hook. Forgetting this flag has no visible failure mode at the call site
972+ // — the run silently hangs after cutover.
973+ // TODO(#8209 commit 2): implement T4 flag set.
974+
975+ // ----- T5: fire on-success hook -----
976+ // mgtr.hooksExecutor.OnSuccess(false). Hook unlocks user_rw@target via
977+ // db-user-management and flips the write_cutover? feature flag.
978+ // Standard env vars only — GH_OST_DRAIN_GTID + GH_OST_TARGET_* are #8211
979+ // (1.7), not this PR.
980+ // TODO(#8209 commit 2): implement T5 hook call with error propagation.
981+
982+ // ----- T6: return nil -----
983+ return nil
984+ }
985+
907986// ExecOnFailureHook executes the onFailure hook, and this method is provided as the only external
908987// hook access point
909988func (mgtr * Migrator ) ExecOnFailureHook () (err error ) {
0 commit comments