@@ -334,10 +334,11 @@ public class RSRpcServices implements HBaseRPCErrorHandler, AdminService.Blockin
334334
335335  private  ScannerIdGenerator  scannerIdGenerator ;
336336  private  final  ConcurrentMap <String , RegionScannerHolder > scanners  = new  ConcurrentHashMap <>();
337-   // Hold the name of a closed scanner for a while. This is used to keep compatible for old clients 
338-   // which may send next or close request to a region scanner which has already been exhausted. The 
339-   // entries will be removed automatically after scannerLeaseTimeoutPeriod. 
340-   private  final  Cache <String , String > closedScanners ;
337+   // Hold the name and last sequence number of a closed scanner for a while. This is used 
338+   // to keep compatible for old clients which may send next or close request to a region 
339+   // scanner which has already been exhausted. The entries will be removed automatically 
340+   // after scannerLeaseTimeoutPeriod. 
341+   private  final  Cache <String , Long > closedScanners ;
341342  /** 
342343   * The lease timeout period for client scanners (milliseconds). 
343344   */ 
@@ -3090,8 +3091,18 @@ private RegionScannerHolder getRegionScanner(ScanRequest request) throws IOExcep
30903091    RegionScannerHolder  rsh  = this .scanners .get (scannerName );
30913092    if  (rsh  == null ) {
30923093      // just ignore the next or close request if scanner does not exists. 
3093-       if  (closedScanners .getIfPresent (scannerName ) != null ) {
3094-         throw  SCANNER_ALREADY_CLOSED ;
3094+       Long  lastCallSeq  = closedScanners .getIfPresent (scannerName );
3095+       if  (lastCallSeq  != null ) {
3096+         // Check the sequence number to catch if the last call was incorrectly retried. 
3097+         // The only allowed scenario is when the scanner is exhausted and one more scan 
3098+         // request arrives - in this case returning 0 rows is correct. 
3099+         if  (request .hasNextCallSeq () && request .getNextCallSeq () != lastCallSeq  + 1 ) {
3100+           throw  new  OutOfOrderScannerNextException ("Expected nextCallSeq for closed request: " 
3101+             + (lastCallSeq  + 1 ) + " But the nextCallSeq got from client: " 
3102+             + request .getNextCallSeq () + "; request="  + TextFormat .shortDebugString (request ));
3103+         } else  {
3104+           throw  SCANNER_ALREADY_CLOSED ;
3105+         }
30953106      } else  {
30963107        LOG .warn ("Client tried to access missing scanner "  + scannerName );
30973108        throw  new  UnknownScannerException (
@@ -3662,7 +3673,7 @@ public ScanResponse scan(final RpcController controller, final ScanRequest reque
36623673      }
36633674      if  (!builder .getMoreResults () || !builder .getMoreResultsInRegion () || closeScanner ) {
36643675        scannerClosed  = true ;
3665-         closeScanner (region , scanner , scannerName , rpcCall );
3676+         closeScanner (region , scanner , scannerName , rpcCall ,  false );
36663677      }
36673678      return  builder .build ();
36683679    } catch  (IOException  e ) {
@@ -3672,7 +3683,7 @@ public ScanResponse scan(final RpcController controller, final ScanRequest reque
36723683        // The scanner state might be left in a dirty state, so we will tell the Client to 
36733684        // fail this RPC and close the scanner while opening up another one from the start of 
36743685        // row that the client has last seen. 
3675-         closeScanner (region , scanner , scannerName , rpcCall );
3686+         closeScanner (region , scanner , scannerName , rpcCall ,  true );
36763687
36773688        // If it is a DoNotRetryIOException already, throw as it is. Unfortunately, DNRIOE is 
36783689        // used in two different semantics. 
@@ -3736,7 +3747,7 @@ private void runShippedCallback(RegionScannerHolder rsh) throws ServiceException
37363747  }
37373748
37383749  private  void  closeScanner (HRegion  region , RegionScanner  scanner , String  scannerName ,
3739-     RpcCallContext  context ) throws  IOException  {
3750+     RpcCallContext  context ,  boolean   isError ) throws  IOException  {
37403751    if  (region .getCoprocessorHost () != null ) {
37413752      if  (region .getCoprocessorHost ().preScannerClose (scanner )) {
37423753        // bypass the actual close. 
@@ -3753,7 +3764,9 @@ private void closeScanner(HRegion region, RegionScanner scanner, String scannerN
37533764      if  (region .getCoprocessorHost () != null ) {
37543765        region .getCoprocessorHost ().postScannerClose (scanner );
37553766      }
3756-       closedScanners .put (scannerName , scannerName );
3767+       if  (!isError ) {
3768+         closedScanners .put (scannerName , rsh .getNextCallSeq ());
3769+       }
37573770    }
37583771  }
37593772
0 commit comments