Valkka  0.17.0
OpenSource Video Management
ValkkaFSManager

ValkkaFS is using both cpp-to-python and python-to-cpp calls.

Writing, reading and caching frames

The level 2 API Python class ValkkaFSManager, uses several level 1 API (core) Python class objects

The logic of requesting certain blocks in order to show (and buffer) frames for a certain time instant is handled completely at the python side

This orchestration is handled by the level 2 API Python class ValkkaFSManager.

Let's use the following pseudocode notation, to see how objects are contained within other objects:

classname(init parameter) {
    classnames of contained objects
}

This is how it looks like. Let's hope you'll get the big picture. :)

api2.ValkkaFSManager(api2.ValkkaFS) {
    
    1: api2.ValkkaFS {
        core.ValkkaFS
        
        # functions called from the c++ side:
        def new_block_cb__(propagate, par:
            - propagate indicates if further callbacks should be evoked
            - par is an integer (block number) or an error string
        }
            
    2: core.ValkkaFSReaderThread {
        core.ValkkaFS
        - writes to core.FileCacherThread.getFrameFilter() [4]
        - frames are requested on per-block basis
        }
    3: core.ValkkaFSWriterThread {
        core.ValkkaFS
        - input framefilter can be requested with getFrameFilter()
        }
    4: core.FileCacherThread {
        - receives seek, play, stop, operations
    
        }   
    
    # functions called from the c++ side:
    
    def timeCallback__(mstime: int):
        - originates from core.FileCacherThread
        - once per 300 ms
        
    def timeLimitsCallback__(tup: tuple):
        - originates from core.FileCacherThread
        - sent when frame cache has been updated
    
    
    # some important methods:
    
    def setOutput(_id, slot, framefilter [**]):
        """Set id => slot mapping.  Set output framefilter
        """
        core.ValkkaFSReaderThread.setSlotIdCall(slot, _id)  # ID-TO-SLOT MAPPING
        ctx = core.FileStreamContext(slot, framefilter)     # SLOT-TO-FRAMEFILTER MAPPING [**]
        core.FileCacherThread.registerStreamCall(ctx)

    def setInput(_id, slot):
        core.ValkkaFSWriterThread.setSlotIdCall(_id, slot)

    def getInputFrameFilter():
        return ValkkaFSWriterThread.getFrameFilter()
        
    }

Frames are transported like this:

outgoing frames:
    
    core.ValkkaFSReaderThread [2] --> core.FileCacherThread [4] --> output framefilter [**]
    
     - Request blocks of frames        - Set seek point, play,
       to be sent downstream             stop, etc. 
     - Uses shared core.ValkkaFS      
       instance
    
    
incoming frames:

    --> core.ValkkaFSWriterThread.getFrameFilter() --> core.ValkkaFSWriterThread
                                                       
                                                       - Updates shared core.ValkkaFS
                                                         instance

ValkkaFS is using both cpp-to-python and python-to-cpp calls

This can get tricky, and care must be taken to avoid nasty deadlocks due to the Python Global Interpreter Lock (GIL)

The cpp part of the code can decide to call some python code, that has been defined in the main thread Python part. This is done by the cpp code "autonomously" and is not initiated from the python side. (*)

Python part might evoke some cpp code. (**)

Special care must be taken with "callback cascades" that start from cpp and end up back to the cpp side again.

The acquisition and release of GIL is illustrated in the following graph.

(*)      = callback from cpp to Python
(**)     = callback from Python to cpp
|        = the instance holding the GIL
DEADLOCK = example deadlock situations

A program using both (*) and (**) callbacks:


            main thread (Python)           cpp thread
            
               |
               |
               |
                                           | acquire GIL (*) 
                                           | - call python method, defined at main thread
                                           | release GIL
               |
               |
               |
                                           | acquire GIL (*)
                                           | - call python method, defined at main thread
                                           |   - that python method might call cpp code which tries to acquire GIL => DEADLOCK!
                                           | release GIL
            
               |
               | 
               | call swig-wrapped   
               | cpp method (**)           do not acquire GIL           
               |                           as it's being hold by the main thread
               |                           return
               |
                                           | acquire GIL (*)
                                           | - call python method, defined at main thread
                                           | release GIL 
            main thread (Python)           ValkkaFSWriterThread    
            
              |
              |
                                           | acquire GIL
                                           | - call ValkkaFSWriterThread::pyfunc
                                           |   - In the python side, this is set to valkka.api2.valkkafs.new_block_cb__
                                           |   - .. which in turn continues the callback-cascade into ValkkaFS::setArrayCall
                                           | release GIL
              |
              |
            
              | requestStopCall           requestStopCall (cpp side)
              | (python side)             - sends message to thread's message queue
              |                           - returns immediately
             
              |                           exits thread's running loop
               
              | waitStopCall              preJoin 
              |                           => saveCurrentBlock 
              |                              => valkkaFS::writeBlock 
              |                                 - should not acquire GIL as this has been 
              |                                   requested from the python side
              |                           thread join & exit
            
            --------------------------------------------------
            
                                           | CacheStream::run
                                           | pyfunc
                                                => valkka.api2.ValkkaFSManager.timeCallback__(mstime)
                                                    => valkka.api2.ValkkaFSManager.stop()
                                                        => ValkkaFSWriterThread::stopStreamsCall() # this makes any sense?
                                                - it's ok to call other thread's functions, as long as they don't return the callback chain to python