Esempio n. 1
0
    def __init__(self, directory ,**kwargs ):
        """ Create a new controller object for our simplistic TxF implementation

            recover :bool - False to force skipping journal recovery.
            reserve : int - initial minimum size for log file.
        """ 
        self.home  = directory
        self.track_state = kwargs.get('track_state',True)

        self.last_opid = -1        #cleanup
        try:
            os.remove(tstfile) 
        except:
            pass 


        #id_lock prevents new transactions being created 
        # and protect last_opid.
        self.id_lock = threading.Lock()     
        #not_complete_lock protects the not_complete list, prevents
        #transaction being submitted and is used for the 
        #outstanding condition variable. Always acquire not_complete_lock
        #before id_lock
        self.oldlogtx = -1
        self.tx = None

        if kwargs.pop('recover',True): self._recover()
        self.frozen = False        #FIXME: In the case that this is a new file
        #we also should sync the directory fd (which means opening one).
 
        self.logf  = None
        self.in_use_logs = []
        self.logsync = threading.Semaphore(0)
        self.loglocker = RWLock()
        self.rotatelog(**kwargs)
Esempio n. 2
0
class FileLoggerSimpleFS(object):
    def __init__(self, directory ,**kwargs ):
        """ Create a new controller object for our simplistic TxF implementation

            recover :bool - False to force skipping journal recovery.
            reserve : int - initial minimum size for log file.
        """ 
        self.home  = directory
        self.track_state = kwargs.get('track_state',True)

        self.last_opid = -1        #cleanup
        try:
            os.remove(tstfile) 
        except:
            pass 


        #id_lock prevents new transactions being created 
        # and protect last_opid.
        self.id_lock = threading.Lock()     
        #not_complete_lock protects the not_complete list, prevents
        #transaction being submitted and is used for the 
        #outstanding condition variable. Always acquire not_complete_lock
        #before id_lock
        self.oldlogtx = -1
        self.tx = None

        if kwargs.pop('recover',True): self._recover()
        self.frozen = False        #FIXME: In the case that this is a new file
        #we also should sync the directory fd (which means opening one).
 
        self.logf  = None
        self.in_use_logs = []
        self.logsync = threading.Semaphore(0)
        self.loglocker = RWLock()
        self.rotatelog(**kwargs)

    def _findlogs(self,):
        logs = glob.glob(os.path.join(*self._get_logname("*")))
        modlogger.debug( "Logs:%s"%logs)

        return logs

    def _newname(self,):
        names = self._findlogs()
        avail = list(set(names) - set(self.in_use_logs))
        if avail:
            return os.path.join(self.home, avail[0])
        else:
            path,  zeroth_log = self._get_logname(-1)
            nr = max([x.split(".")[3] for x in chain([zeroth_log],names)])
            nr = int(nr) +1
            return os.path.join(*self._get_logname(nr))

    def _get_logname(self,seq):
        """Takes integer decribing the sequence nr of the log file,
        or * to generate a glob pattern for logfiles"""
        return self.home,"..xaction.%s.log"%seq

    def new_opid(self,):
        """Internal: Create a new unique transaction id (Thread safe)"""
        with self.id_lock:
            v = self.last_opid + 1
            self.last_opid = v
        return v
   

    def _recover(self,):
        """Replay associated logfiles. This function blocks
        until completion."""
        modlogger.debug( "starting recovery")
        with self.id_lock: #Prevent new ops being created.
            logs = [ LogFile(x,readonly=True) for x in self._findlogs() ]      
            logiter = [ iter(x) for x in logs ]
            ops   = [ _getop(x) for x in logiter ]
            opids = [ _getid(x) for x in ops ]
            #order the log files by operation Id.
            data = sorted(izip(logs,logiter,ops,opids),key =lambda x:x[3])
            modlogger.debug( "SR:%s"%data)
            #And now got through all log files in Id order
            state = 'init'
            unrecoverable = []
            for log,it,op,opid in data:
                for cur_op in chain([op],it):
                    #cur_op None indicated end of that logfile.
                    if cur_op is None: break

                    #We ignore any ops until we see a 'startTxn' marker, but we
                    # keep a record of there ids to ensure we see a later checkpoint.
                    # if we don't we can't replay partial Txn.
                    modlogger.debug( "R:",cur_op,state)
                    if state=='init':
                        #Record all operations we see before we see the first
                        #start tx marker.
                        if cur_op.optype == 'start_txn':
                             state='txcomplete'
                        elif cur_op.optype == 'abort_txn':
                            #If the partial transaction we found was aborted
                            # we don't need to worry about its operations. 
                            unrcoverable = [ ]
                        elif cur_op.optype == 'Checkpoint':
                            unrecoverable = _remove_commited(unrecoverable,cur_op.opid)
                        else:
                            unrecoverable += [ op.opid]
                    

                    #We are looking for a starttxn, marker to mark the operation
                    #as valid. The only other meaningful transaction in the
                    #journal in the state is a checkpoint making which ops have been
                    #detected as committed to the main store by the FS.
                    if state=='txcomplete':
                        if cur_op.optype == 'start_txn':
                            tx = cur_op.txn_id
                            self.txops = [ ]
                            state = 'txstarted'
                            continue
                        elif cur_op.optype == 'Checkpoint':
                            unrecoverable = _remove_commited(unrecoverable,cur_op.opid)
                        else: raise RecoveryError("Operation outside tx")

                    #In this state all operations are meaningful.
                    # we store all operations (except checkpoint) until we see
                    # a EndTxn op. At the end TxnOp we synchronously complete
                    # all operations.
                    if state =='txstarted':
                        if cur_op.optype == 'end_txn': 
                            #The test below finds 'overlapped' tx, (or ones missing a commit record
                            #for  some reason. This forces us not to accept this log file.
                            if cur_op.txn_id != tx: raise RecoveryError("Non matching Tx commit found")
                            else:
                                for top in self.txops:
                                    top.do(sync = True)
                            state = 'txcomplete'
                        elif cur_op.optype == 'abort_txn':
                            state = 'txcomplete'
                        elif cur_op.optype == 'Checkpoint':
                            unrecoverable = _remove_commited(unrecoverable,cur_op.opid)
                        else:
                            self.txops += [ cur_op ] 
                #Log file has been processed successfully - remove it from the Fs.
                #we could call close() here and reused the allocated space on the
                #FS - but the logfile is readonly - and close() adds a terminator
                #to mark the file as empty.
                try:
                    log.unlink()
                except OSError: pass

            #If there are any partial txn's left we have failed to recover.
            if unrecoverable: raise RecoveryError("Partial uncommitted txn found")
            
    def rotatelog(self,**kwargs):
        """Create a new log file ready for use and place , maek it the
        active file"""
        newname = self._newname()
        newlgf = LogFile(newname,**kwargs)
        with self.id_lock:
            self._rotatelog(newlgf,newname)

    def _rotatelog(self,newlgf,newname):
        """Internal function. Should be called with id_lock held"""
        modlogger.debug( "rl:%s"%newname)
        if self.logf: 
            thread.start_new_thread(self._waitlog,(self.logf,self.logname))
            self.logsync.acquire()

        if newname: self.in_use_logs += [ newname ] 
        try:
            self.logf, self.logname = newlgf , newname
        except:
            if newname:
                self.in_use_logs.remove(newname)
            raise

    def _waitlog(self,logf,fname):
        self.loglocker.acquire_read()
        self.logsync.release()
        logf.close()
        self.in_use_logs.remove(fname)
        self.loglocker.release()

    def Add_File(self,txn,filename,newcontents):
        """Log new file content transaction."""
        opid = self.new_opid()
        fullname = os.path.join(self.home,filename)
        #if not self.tx.dir_exists(os.path.dirname(fullname)):
        #    raise OSError(errno.ENOENT,"No directory: %r"%os.path.dirname(fullname))
        xaction = ReplaceAll_Operation(fullname,newcontents,opid)
        self._add_operation(txn,xaction)
 
    def Delete_File(self,txn,filename):
        """Log a delete file  transaction."""
        opid = self.new_opid()
        xaction = DeleteFile_Operation(os.path.join(self.home,filename),opid)
        self._add_operation(txn,xaction)
 
    def Create_Dir(self,txn,filename):
        """Log a delete file  transaction."""
        opid = self.new_opid()
        xaction = CreateDir_Operation(os.path.join(self.home,filename),opid)
        self._add_operation(txn,xaction)
 
    def Delete_Dir(self,txn,filename):
        """Log a delete file  transaction."""
        opid = self.new_opid()
        xaction = DeleteDir_Operation(os.path.join(self.home,filename),opid)
        self._add_operation(txn,xaction)
         
    def _add_operation(self,txn,xaction):
        modlogger.debug( "ao")
        if txn != self.tx.xid: raise RuntimeError("wrong txn %s!=%s"%(txn,self.tx.xid))
        self.tx.add(xaction)
        while not ( self.logf and self.logf.append(xaction)):
            self.rotatelog(reserve = len(xaction))

    def start_transaction(self,):
        """Start a new transaction to group operations.

        Returns: an opaque obj for use with other mehtods"""

        if self.tx is not None:
            raise OverlappedTransaction(str(self.tx.xid))

        modlogger.debug("start tx")
        opid = self.new_opid()
        xaction = StartTxOperation(opid,opid)
        self.tx = Transaction(opid,self.home, track_state = self.track_state)
        self._add_operation(opid,xaction)
        return opid

    def commit_transaction(self,xid):
        """Commit the tranasaction with id xid.

        This forces all the operations which have been logged as
        part of this transaction to persistent store.
        """
        modlogger.debug( "end tx:%s"%xid)
        if xid != self.tx.xid:
            raise InvalidTransaction(xid)

        opid = self.new_opid()
        xaction = EndTxOperation(opid,xid)
        self._add_operation(xid,xaction)
        try:
            self.tx.commit()
        except:
            self.tx = None
            #There is an arguement to call abort here...
            # but we don't because our (MysteryMachine) use
            # of this is a bottom layer store, which means the
            # schema should have already ensured the transactions
            # are meaningful and a exceptio here is probably a
            # due to local conditions (like diskspace).
            #
            # If we abort we would have to remove the commit record
            # from the log, and then undo the partial tranasction.
            # by not aborting, and re-raisig we make this the 
            # problem of a higher layer. Once the problem is resolved
            # the journal can be replayied making ourselve consistent
            # again.
            #
            # There is one way though of provoking non-recoverable behavour
            # ad that is creating a file in a non-existant dir.
           
            #self.abort_transaction(xid)
            raise

        self.tx = None

    def abort_transaction(self,xid):
        """Aborts the transaction with id xid.

        This forces all the operations which have been logged as
        part of this transaction to persistent store.
        """
        modlogger.debug( "abort:%s"%xid)
        opid = self.new_opid()
        xaction = AbortTxOperation(opid,xid)
        self._add_operation(xid,xaction)
        try:
            self.tx.rollback()
        finally:
            self.tx = None


    def close(self,):
        """Finish use of the File logged directory"""
        self.freeze()
        

    def freeze(self,):
        """Bring main store upto date and prevent any more
        transactions while frozen"""
        if self.frozen: return

        self.id_lock.acquire()
        #Set logfile to None. Put current logfile into wait for chkpt state.
        self._rotatelog(None,"")
        self.loglocker.acquire_write()
        self.frozen = True     
 
    def unfreeze(self,):
        """Release any transactions from being blocked by the frozen state."""
        if self.frozen and self.id_lock.locked():
            self.id_lock.release()
            self.loglocker.release()
            self.frozen = False
    
    thaw = unfreeze

    def _del_(self,):
        if not self.frozen:
            #Depend on nothing outside this object
            import logging
            logging.getLogger("MysteryMachine.store.FileLogger").error("Detected cleanup of Fileloogger incorrectly closed")