[ 
https://issues.apache.org/jira/browse/HBASE-30357?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Aman Poonia updated HBASE-30357:
--------------------------------
    Description: 
On master-failover restore, RegionRemoteProcedureBase.stateLoaded() calls
restoreSucceedState(am, regionNode, seqId) whenever the persisted 
child-procedure
state is REGION_REMOTE_PROCEDURE_REPORT_SUCCEED. OpenRegionProcedure's override:
{code:java}
  protected void restoreSucceedState(AssignmentManager am, RegionStateNode 
regionNode,
      long openSeqNum) throws IOException {
    if (regionNode.getState() == State.OPEN) {
      return;
    }
    regionOpenedWithoutPersistingToMeta(am, regionNode, TransitionCode.OPENED, 
openSeqNum);
  }
  {code}
unconditionally forces {{{}TransitionCode.OPENED{}}}, regardless of what the 
RegionServer
actually reported. The real outcome (OPENED vs FAILED_OPEN) is persisted in the
procedure's own {{transitionCode}} field, but the method's signature only 
receives
{{seqId}} and structurally cannot see it.

Concretely: if a RegionServer reports FAILED_OPEN, the master persists
state=REPORT_SUCCEED + transitionCode=FAILED_OPEN to the procedure store, but 
does
NOT change RegionState.State from OPENING 
({{{}AssignmentManager#regionFailedOpen{}}} with
{{giveUp=false}} is a no-op on state). If the master fails over before 
persistToMeta
runs, restoreSucceedState() runs on reload, sees state=OPENING (which satisfies 
the
guard

{OPENING, OPEN} on regionOpenedWithoutPersistingToMeta), and force-transitions
the region to OPEN – even though it was never actually opened on any 
RegionServer.
{{TransitRegionStateProcedure#confirmOpened()}} has no independent check; it 
only reads
{{{}regionNode.isInState(OPEN){}}}, the same field this bug corrupts. There is 
no rollback
anywhere in this procedure chain (by design – forward-only), so nothing 
downstream
can detect or correct the false OPEN once persisted to hbase:meta.

By contrast, {{CloseRegionProcedure#restoreSucceedState}} is safe by 
construction: CLOSE
has no FAILED_CLOSE variant at the master side (see UnassignRegionHandler.java
comment), so forcing CLOSED on restore is always correct.

h3. Proposed fix

Thread the real {{transitionCode}} field through
{{RegionRemoteProcedureBase#stateLoaded()}} into restoreSucceedState(), and have
OpenRegionProcedure branch on it exactly like the live path
({{{}updateTransitionWithoutPersistingToMeta{}}}) already does:
* OPENED -> regionOpenedWithoutPersistingToMeta (current behavior)
* FAILED_OPEN -> am.regionFailedOpen(regionNode, false), letting
TransitRegionStateProcedure#confirmOpened() retry/reassign normally.

This only requires widening one abstract method's signature; 
CloseRegionProcedure's
override can ignore the new parameter (no CLOSE failure variant exists).

h3. Not a regression

Confirmed via git archaeology: restoreSucceedState() was introduced with 
exactly this
logic in HBASE-22365 (2019-05-10) and has been unchanged (modulo spotless 
formatting)
through every subsequent release and backport.
EOF)
⎿  On master-failover restore, RegionRemoteProcedureBase.stateLoaded() calls
restoreSucceedState(am, regionNode, seqId) whenever the persisted 
child-procedure
state is REGION_REMOTE_PROCEDURE_REPORT_SUCCEED. OpenRegionProcedure's override:


{code:java}
     protected void restoreSucceedState(AssignmentManager am, RegionStateNode 
regionNode,
         long openSeqNum) throws IOException {
       if (regionNode.getState() == State.OPEN) {
         return;
       }
       regionOpenedWithoutPersistingToMeta(am, regionNode, 
TransitionCode.OPENED, openSeqNum);
     }
     {code}


unconditionally forces {{{}TransitionCode.OPENED{}}}, regardless of what the 
RegionServer
actually reported. The real outcome (OPENED vs FAILED_OPEN) is persisted in the
procedure's own {{transitionCode}} field, but the method's signature only 
receives
{{seqId}} and structurally cannot see it.

Concretely: if a RegionServer reports FAILED_OPEN, the master persists
state=REPORT_SUCCEED + transitionCode=FAILED_OPEN to the procedure store, but 
does
NOT change RegionState.State from OPENING 
({{{}AssignmentManager#regionFailedOpen{}}} with
{{giveUp=false}} is a no-op on state). If the master fails over before 
persistToMeta
runs, restoreSucceedState() runs on reload, sees state=OPENING (which satisfies 
the
guard \{OPENING, OPEN}

on regionOpenedWithoutPersistingToMeta), and force-transitions
the region to OPEN – even though it was never actually opened on any 
RegionServer.
{{TransitRegionStateProcedure#confirmOpened()}} has no independent check; it 
only reads
{{{}regionNode.isInState(OPEN){}}}, the same field this bug corrupts. There is 
no rollback
anywhere in this procedure chain (by design – forward-only), so nothing 
downstream
can detect or correct the false OPEN once persisted to hbase:meta.

By contrast, {{CloseRegionProcedure#restoreSucceedState}} is safe by 
construction: CLOSE
has no FAILED_CLOSE variant at the master side (see UnassignRegionHandler.java
comment), so forcing CLOSED on restore is always correct.
h3. Proposed fix

Thread the real {{transitionCode}} field through
{{RegionRemoteProcedureBase#stateLoaded()}} into restoreSucceedState(), and have
OpenRegionProcedure branch on it exactly like the live path
({{{}updateTransitionWithoutPersistingToMeta{}}}) already does:
 * OPENED -> regionOpenedWithoutPersistingToMeta (current behavior)
 * FAILED_OPEN -> am.regionFailedOpen(regionNode, false), letting
TransitRegionStateProcedure#confirmOpened() retry/reassign normally.

This only requires widening one abstract method's signature; 
CloseRegionProcedure's
override can ignore the new parameter (no CLOSE failure variant exists).
h3. Not a regression

Confirmed via git archaeology: restoreSucceedState() was introduced with 
exactly this
logic in HBASE-22365 (2019-05-10) and has been unchanged (modulo spotless 
formatting)
through every subsequent release and backport.

  was:
On master-failover restore, RegionRemoteProcedureBase.stateLoaded() calls
  restoreSucceedState(am, regionNode, seqId) whenever the persisted 
child-procedure
  state is REGION_REMOTE_PROCEDURE_REPORT_SUCCEED. OpenRegionProcedure's 
override:

  {code:java}
  protected void restoreSucceedState(AssignmentManager am, RegionStateNode 
regionNode,
      long openSeqNum) throws IOException {
    if (regionNode.getState() == State.OPEN) {
      return;
    }
    regionOpenedWithoutPersistingToMeta(am, regionNode, TransitionCode.OPENED, 
openSeqNum);
  }
  {code}

  unconditionally forces {{TransitionCode.OPENED}}, regardless of what the 
RegionServer
  actually reported. The real outcome (OPENED vs FAILED_OPEN) is persisted in 
the
  procedure's own {{transitionCode}} field, but the method's signature only 
receives
  {{seqId}} and structurally cannot see it.

  Concretely: if a RegionServer reports FAILED_OPEN, the master persists
  state=REPORT_SUCCEED + transitionCode=FAILED_OPEN to the procedure store, but 
does
  NOT change RegionState.State from OPENING 
({{AssignmentManager#regionFailedOpen}} with
  {{giveUp=false}} is a no-op on state). If the master fails over before 
persistToMeta
  runs, restoreSucceedState() runs on reload, sees state=OPENING (which 
satisfies the
  guard {OPENING, OPEN} on regionOpenedWithoutPersistingToMeta), and 
force-transitions
  the region to OPEN -- even though it was never actually opened on any 
RegionServer.
  {{TransitRegionStateProcedure#confirmOpened()}} has no independent check; it 
only reads
  {{regionNode.isInState(OPEN)}}, the same field this bug corrupts. There is no 
rollback
  anywhere in this procedure chain (by design -- forward-only), so nothing 
downstream
  can detect or correct the false OPEN once persisted to hbase:meta.

  By contrast, {{CloseRegionProcedure#restoreSucceedState}} is safe by 
construction: CLOSE
  has no FAILED_CLOSE variant at the master side (see UnassignRegionHandler.java
  comment), so forcing CLOSED on restore is always correct.

  h3. Proposed fix

  Thread the real {{transitionCode}} field through
  {{RegionRemoteProcedureBase#stateLoaded()}} into restoreSucceedState(), and 
have
  OpenRegionProcedure branch on it exactly like the live path
  ({{updateTransitionWithoutPersistingToMeta}}) already does:
  * OPENED -> regionOpenedWithoutPersistingToMeta (current behavior)
  * FAILED_OPEN -> am.regionFailedOpen(regionNode, false), letting
    TransitRegionStateProcedure#confirmOpened() retry/reassign normally.

  This only requires widening one abstract method's signature; 
CloseRegionProcedure's
  override can ignore the new parameter (no CLOSE failure variant exists).

  h3. Not a regression

  Confirmed via git archaeology: restoreSucceedState() was introduced with 
exactly this
  logic in HBASE-22365 (2019-05-10) and has been unchanged (modulo spotless 
formatting)
  through every subsequent release and backport.
  EOF)
  ⎿  On master-failover restore, RegionRemoteProcedureBase.stateLoaded() calls
     restoreSucceedState(am, regionNode, seqId) whenever the persisted 
child-procedure
     state is REGION_REMOTE_PROCEDURE_REPORT_SUCCEED. OpenRegionProcedure's 
override:

     {code:java}
     protected void restoreSucceedState(AssignmentManager am, RegionStateNode 
regionNode,
         long openSeqNum) throws IOException {
       if (regionNode.getState() == State.OPEN) {
         return;
       }
       regionOpenedWithoutPersistingToMeta(am, regionNode, 
TransitionCode.OPENED, openSeqNum);
     }
     {code}

     unconditionally forces {{TransitionCode.OPENED}}, regardless of what the 
RegionServer
     actually reported. The real outcome (OPENED vs FAILED_OPEN) is persisted 
in the
     procedure's own {{transitionCode}} field, but the method's signature only 
receives
     {{seqId}} and structurally cannot see it.

     Concretely: if a RegionServer reports FAILED_OPEN, the master persists
     state=REPORT_SUCCEED + transitionCode=FAILED_OPEN to the procedure store, 
but does
     NOT change RegionState.State from OPENING 
({{AssignmentManager#regionFailedOpen}} with
     {{giveUp=false}} is a no-op on state). If the master fails over before 
persistToMeta
     runs, restoreSucceedState() runs on reload, sees state=OPENING (which 
satisfies the
     guard {OPENING, OPEN} on regionOpenedWithoutPersistingToMeta), and 
force-transitions
     the region to OPEN -- even though it was never actually opened on any 
RegionServer.
     {{TransitRegionStateProcedure#confirmOpened()}} has no independent check; 
it only reads
     {{regionNode.isInState(OPEN)}}, the same field this bug corrupts. There is 
no rollback
     anywhere in this procedure chain (by design -- forward-only), so nothing 
downstream
     can detect or correct the false OPEN once persisted to hbase:meta.

     By contrast, {{CloseRegionProcedure#restoreSucceedState}} is safe by 
construction: CLOSE
     has no FAILED_CLOSE variant at the master side (see 
UnassignRegionHandler.java
     comment), so forcing CLOSED on restore is always correct.

     h3. Proposed fix

     Thread the real {{transitionCode}} field through
     {{RegionRemoteProcedureBase#stateLoaded()}} into restoreSucceedState(), 
and have
     OpenRegionProcedure branch on it exactly like the live path
     ({{updateTransitionWithoutPersistingToMeta}}) already does:
     * OPENED -> regionOpenedWithoutPersistingToMeta (current behavior)
     * FAILED_OPEN -> am.regionFailedOpen(regionNode, false), letting
       TransitRegionStateProcedure#confirmOpened() retry/reassign normally.

     This only requires widening one abstract method's signature; 
CloseRegionProcedure's
     override can ignore the new parameter (no CLOSE failure variant exists).

     h3. Not a regression

     Confirmed via git archaeology: restoreSucceedState() was introduced with 
exactly this
     logic in HBASE-22365 (2019-05-10) and has been unchanged (modulo spotless 
formatting)
     through every subsequent release and backport.


> OpenRegionProcedure#restoreSucceedState ignores persisted transitionCode, 
> forcing OPEN even after a real FAILED_OPEN
> --------------------------------------------------------------------------------------------------------------------
>
>                 Key: HBASE-30357
>                 URL: https://issues.apache.org/jira/browse/HBASE-30357
>             Project: HBase
>          Issue Type: Bug
>          Components: proc-v2, Region Assignment
>            Reporter: Aman Poonia
>            Assignee: Aman Poonia
>            Priority: Major
>
> On master-failover restore, RegionRemoteProcedureBase.stateLoaded() calls
> restoreSucceedState(am, regionNode, seqId) whenever the persisted 
> child-procedure
> state is REGION_REMOTE_PROCEDURE_REPORT_SUCCEED. OpenRegionProcedure's 
> override:
> {code:java}
>   protected void restoreSucceedState(AssignmentManager am, RegionStateNode 
> regionNode,
>       long openSeqNum) throws IOException {
>     if (regionNode.getState() == State.OPEN) {
>       return;
>     }
>     regionOpenedWithoutPersistingToMeta(am, regionNode, 
> TransitionCode.OPENED, openSeqNum);
>   }
>   {code}
> unconditionally forces {{{}TransitionCode.OPENED{}}}, regardless of what the 
> RegionServer
> actually reported. The real outcome (OPENED vs FAILED_OPEN) is persisted in 
> the
> procedure's own {{transitionCode}} field, but the method's signature only 
> receives
> {{seqId}} and structurally cannot see it.
> Concretely: if a RegionServer reports FAILED_OPEN, the master persists
> state=REPORT_SUCCEED + transitionCode=FAILED_OPEN to the procedure store, but 
> does
> NOT change RegionState.State from OPENING 
> ({{{}AssignmentManager#regionFailedOpen{}}} with
> {{giveUp=false}} is a no-op on state). If the master fails over before 
> persistToMeta
> runs, restoreSucceedState() runs on reload, sees state=OPENING (which 
> satisfies the
> guard
> {OPENING, OPEN} on regionOpenedWithoutPersistingToMeta), and force-transitions
> the region to OPEN – even though it was never actually opened on any 
> RegionServer.
> {{TransitRegionStateProcedure#confirmOpened()}} has no independent check; it 
> only reads
> {{{}regionNode.isInState(OPEN){}}}, the same field this bug corrupts. There 
> is no rollback
> anywhere in this procedure chain (by design – forward-only), so nothing 
> downstream
> can detect or correct the false OPEN once persisted to hbase:meta.
> By contrast, {{CloseRegionProcedure#restoreSucceedState}} is safe by 
> construction: CLOSE
> has no FAILED_CLOSE variant at the master side (see UnassignRegionHandler.java
> comment), so forcing CLOSED on restore is always correct.
> h3. Proposed fix
> Thread the real {{transitionCode}} field through
> {{RegionRemoteProcedureBase#stateLoaded()}} into restoreSucceedState(), and 
> have
> OpenRegionProcedure branch on it exactly like the live path
> ({{{}updateTransitionWithoutPersistingToMeta{}}}) already does:
> * OPENED -> regionOpenedWithoutPersistingToMeta (current behavior)
> * FAILED_OPEN -> am.regionFailedOpen(regionNode, false), letting
> TransitRegionStateProcedure#confirmOpened() retry/reassign normally.
> This only requires widening one abstract method's signature; 
> CloseRegionProcedure's
> override can ignore the new parameter (no CLOSE failure variant exists).
> h3. Not a regression
> Confirmed via git archaeology: restoreSucceedState() was introduced with 
> exactly this
> logic in HBASE-22365 (2019-05-10) and has been unchanged (modulo spotless 
> formatting)
> through every subsequent release and backport.
> EOF)
> ⎿  On master-failover restore, RegionRemoteProcedureBase.stateLoaded() calls
> restoreSucceedState(am, regionNode, seqId) whenever the persisted 
> child-procedure
> state is REGION_REMOTE_PROCEDURE_REPORT_SUCCEED. OpenRegionProcedure's 
> override:
> {code:java}
>      protected void restoreSucceedState(AssignmentManager am, RegionStateNode 
> regionNode,
>          long openSeqNum) throws IOException {
>        if (regionNode.getState() == State.OPEN) {
>          return;
>        }
>        regionOpenedWithoutPersistingToMeta(am, regionNode, 
> TransitionCode.OPENED, openSeqNum);
>      }
>      {code}
> unconditionally forces {{{}TransitionCode.OPENED{}}}, regardless of what the 
> RegionServer
> actually reported. The real outcome (OPENED vs FAILED_OPEN) is persisted in 
> the
> procedure's own {{transitionCode}} field, but the method's signature only 
> receives
> {{seqId}} and structurally cannot see it.
> Concretely: if a RegionServer reports FAILED_OPEN, the master persists
> state=REPORT_SUCCEED + transitionCode=FAILED_OPEN to the procedure store, but 
> does
> NOT change RegionState.State from OPENING 
> ({{{}AssignmentManager#regionFailedOpen{}}} with
> {{giveUp=false}} is a no-op on state). If the master fails over before 
> persistToMeta
> runs, restoreSucceedState() runs on reload, sees state=OPENING (which 
> satisfies the
> guard \{OPENING, OPEN}
> on regionOpenedWithoutPersistingToMeta), and force-transitions
> the region to OPEN – even though it was never actually opened on any 
> RegionServer.
> {{TransitRegionStateProcedure#confirmOpened()}} has no independent check; it 
> only reads
> {{{}regionNode.isInState(OPEN){}}}, the same field this bug corrupts. There 
> is no rollback
> anywhere in this procedure chain (by design – forward-only), so nothing 
> downstream
> can detect or correct the false OPEN once persisted to hbase:meta.
> By contrast, {{CloseRegionProcedure#restoreSucceedState}} is safe by 
> construction: CLOSE
> has no FAILED_CLOSE variant at the master side (see UnassignRegionHandler.java
> comment), so forcing CLOSED on restore is always correct.
> h3. Proposed fix
> Thread the real {{transitionCode}} field through
> {{RegionRemoteProcedureBase#stateLoaded()}} into restoreSucceedState(), and 
> have
> OpenRegionProcedure branch on it exactly like the live path
> ({{{}updateTransitionWithoutPersistingToMeta{}}}) already does:
>  * OPENED -> regionOpenedWithoutPersistingToMeta (current behavior)
>  * FAILED_OPEN -> am.regionFailedOpen(regionNode, false), letting
> TransitRegionStateProcedure#confirmOpened() retry/reassign normally.
> This only requires widening one abstract method's signature; 
> CloseRegionProcedure's
> override can ignore the new parameter (no CLOSE failure variant exists).
> h3. Not a regression
> Confirmed via git archaeology: restoreSucceedState() was introduced with 
> exactly this
> logic in HBASE-22365 (2019-05-10) and has been unchanged (modulo spotless 
> formatting)
> through every subsequent release and backport.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to