import * as fs from 'fs' ; import * as os from 'os' ; import * as path from 'path' ; import { CancellationId , CancellationTokenSource } from 'vscode-jsonrpc' ; import { AbstractCancellationTokenSource , CancellationReceiverStrategy , CancellationSenderStrategy , CancellationStrategy , CancellationToken , Emitter , Event , } from 'vscode-languageserver' ; import { CancellationProvider , getCancellationFolderName , setCancellationFolderName } from './cancellationUtils' ; class CancellationThrottle { private static _lastCheckTimestamp = 0 ; static shouldCheck ( ) { const minTimeBetweenChecksInMs = 5 ; const curTimestamp = Date . now ( ) . valueOf ( ) ; const timeSinceLastCheck = curTimestamp - this . _lastCheckTimestamp ; if ( timeSinceLastCheck >= minTimeBetweenChecksInMs ) { this . _lastCheckTimestamp = curTimestamp ; return true ; } return false ; } } class FileBasedToken implements CancellationToken { protected isCancelled = false ; private _emitter : Emitter < any > | undefined ; constructor ( readonly cancellationFilePath ) { } cancel ( ) { if ( ! this . isCancelled ) { this . isCancelled = true ; if ( this . _emitter ) { this . _emitter . fire ( undefined ) ; this . _disposeEmitter ( ) ; } } } get isCancellationRequested ( ) : boolean { if ( this . isCancelled ) { return true ; } if ( CancellationThrottle . shouldCheck ( ) && this . _pipeExists ( ) ) { this . cancel ( ) ; } return this . isCancelled ; } get onCancellationRequested ( ) : Event < any > { if ( ! this . _emitter ) { this . _emitter = new Emitter < any > ( ) ; } return this . _emitter . event ; } dispose ( ) { this . _disposeEmitter ( ) ; } private _disposeEmitter ( ) { if ( this . _emitter ) { this . _emitter . dispose ( ) ; this . _emitter = undefined ; } } private _pipeExists ( ) { try { fs . statSync ( this . cancellationFilePath ) ; return true ; } catch ( e ) { return false ; } } } class OwningFileToken extends FileBasedToken { private _disposed = false ; constructor ( cancellationFilePath ) { super ( cancellationFilePath ) ; } override cancel ( ) { if ( ! this . _disposed && ! this . isCancelled ) { this . _createPipe ( ) ; super . cancel ( ) ; } } override get isCancellationRequested ( ) : boolean { return this . isCancelled ; } override dispose ( ) { this . _disposed = true ; super . dispose ( ) ; this . _removePipe ( ) ; } private _createPipe ( ) { try { fs . writeFileSync ( this . cancellationFilePath , '' , { flag : 'w' } ) ; } catch { } } private _removePipe ( ) { try { fs . unlinkSync ( this . cancellationFilePath ) ; } catch { } } } class FileBasedCancellationTokenSource implements AbstractCancellationTokenSource { private _token : CancellationToken | undefined ; constructor ( private _cancellationFilePath , private _ownFile = false ) { } get token ( ) : CancellationToken { if ( ! this . _token ) { this . _token = this . _ownFile ? new OwningFileToken ( this . _cancellationFilePath ) : new FileBasedToken ( this . _cancellationFilePath ) ; } return this . _token ; } cancel ( ) { if ( ! this . _token ) { this . _token = CancellationToken . Cancelled ; } else { ( this . _token as FileBasedToken ) . cancel ( ) ; } } dispose ( ) { if ( ! this . _token ) { this . _token = CancellationToken . None ; } else if ( this . _token instanceof FileBasedToken ) { this . _token . dispose ( ) ; } } } function getCancellationFolderPath ( folderName ) { return path . join ( os . tmpdir ( ) , 'python-languageserver-cancellation' , folderName ) ; } function getCancellationFilePath ( folderName , id ) { return path . join ( getCancellationFolderPath ( folderName ) , `cancellation-${ String ( id ) }.tmp` ) ; } class FileCancellationReceiverStrategy implements CancellationReceiverStrategy { constructor ( readonly folderName ) { } createCancellationTokenSource ( id ) { return new FileBasedCancellationTokenSource ( getCancellationFilePath ( this . folderName , id ) ) ; } } export function getCancellationStrategyFromArgv ( argv ) { let receiver ; for ( let i = 0 ; i < argv . length ; i ++ ) { const arg = argv [ i ] ; if ( arg === '--cancellationReceive' ) { receiver = createReceiverStrategyFromArgv ( argv [ i + 1 ] ) ; } else { const args = arg . split ( '=' ) ; if ( args [ 0 ] === '--cancellationReceive' ) { receiver = createReceiverStrategyFromArgv ( args [ 1 ] ) ; } } } if ( receiver && ! getCancellationFolderName ( ) ) { setCancellationFolderName ( ( receiver as FileCancellationReceiverStrategy ) . folderName ) ; } receiver = receiver ? receiver : CancellationReceiverStrategy . Message ; return { receiver , sender : CancellationSenderStrategy . Message } ; function createReceiverStrategyFromArgv ( arg ) { const folderName = extractCancellationFolderName ( arg ) ; return folderName ? new FileCancellationReceiverStrategy ( folderName ) : undefined ; } function extractCancellationFolderName ( arg ) { const fileRegex = /^file:(.+)$/ ; const folderName = arg . match ( fileRegex ) ; return folderName ? folderName [ 1 ] : undefined ; } } export function disposeCancellationToken ( token ) { if ( token instanceof FileBasedToken ) { token . dispose ( ) ; } } export function getCancellationTokenFromId ( cancellationId ) { if ( ! cancellationId ) { return CancellationToken . None ; } return new FileBasedToken ( cancellationId ) ; } export function getCancellationTokenId ( token ) { return token instanceof FileBasedToken ? token . cancellationFilePath : undefined ; } let cancellationSourceId = 0 ; export class FileBasedCancellationProvider implements CancellationProvider { constructor ( private _prefix ) { } createCancellationTokenSource ( ) { const folderName = getCancellationFolderName ( ) ; if ( ! folderName ) { return new CancellationTokenSource ( ) ; } return new FileBasedCancellationTokenSource ( getCancellationFilePath ( folderName , `${ this . _prefix }-${ String ( cancellationSourceId ++ ) }` ) , true ) ; } }