Commits

ZyX_I  committed 9d4d6f9

@%aurum, python/aurum/repeatedcmd, @%aurum/drivers/mercurial:
First working implementation of asynchronously repeated commands
(only supported by mercurial python driver)
(only aurum#status and aurum#branch functions are using it)
Added code for pausing/resuming calculations in background processes

  • Participants
  • Parent commits f147323

Comments (0)

Files changed (7)

File autoload/aurum.vim

 "▶1 
 scriptencoding utf-8
-execute frawor#Setup('0.1', {'@%aurum/repo': '5.0',
+execute frawor#Setup('0.1', {'@%aurum/repo': '5.5',
             \                '@aurum/cache': '2.0',
             \            '@%aurum/cmdutils': '4.0',})
 "▶1 getcrf
     endif
     return [cbvar, repo, file]
 endfunction
+"▶1 unload
+function s:._unload()
+    for adesc in values(s:akeys)
+        call adesc.repo.functions.aremove(adesc.repo, adesc.rcid)
+    endfor
+endfunction
+"▶1 anew
+let s:abuffers={}
+let s:akeys={}
+function s:F.anew(buf, repo, key, func, ...)
+    if !has_key(s:abuffers, a:buf)
+        let s:abuffers[a:buf]={}
+    endif
+    let key=a:func.string(a:000).a:repo.path
+    if !has_key(s:akeys, key)
+        let interval=s:_r.cache.getinterval(a:key)
+        let rcid=call(a:repo.functions['a'.a:func], [a:repo,interval]+a:000, {})
+        let s:akeys[key]={'repo': a:repo, 'rcid': rcid, 'buffers': {a:buf : 1},
+                    \      'key': key}
+    else
+        let s:akeys[key].buffers[a:buf]=1
+    endif
+    augroup AuRemoveRC
+        autocmd! BufWipeOut,BufFilePost <buffer> call s:F.abw(+expand('<abuf>'))
+    augroup END
+    let s:abuffers[a:buf][a:key]=s:akeys[key]
+    return s:F.aget(s:akeys[key], 1)
+endfunction
+let s:_augroups+=['AuRemoveRC']
+"▶1 aget
+function s:F.aget(adesc, now)
+    return a:adesc.repo.functions.aget(a:adesc.repo, a:adesc.rcid, a:now)
+endfunction
+"▶1 abw
+function s:F.abw(buf)
+    if has_key(s:abuffers, a:buf)
+        for adesc in values(remove(s:abuffers, a:buf))
+            unlet adesc.buffers[a:buf]
+            if empty(adesc.buffers)
+                unlet s:akeys[adesc.key]
+                call adesc.repo.functions.aremove(adesc.repo, adesc.rcid)
+            endif
+        endfor
+    endif
+endfunction
+"▶1 aswitch
+let s:pexpr='map(values(v:val), '.
+            \   '"v:val.repo.functions.%s(v:val.repo, v:val.rcid)")'
+function s:F.aswitch()
+    let bl=tabpagebuflist()
+    call map(copy(s:abuffers), 'index(bl, +v:key)==-1 ? '.
+                \                   printf(s:pexpr, 'apause').' : '.
+                \                   printf(s:pexpr, 'aresume'))
+endfunction
+augroup AuRCSwitchBuffers
+    autocmd BufEnter * :call s:F.aswitch()
+augroup END
+let s:_augroups+=['AuRCSwitchBuffers']
 "▶1 aurum#repository
 function aurum#repository()
     let repo=s:_r.cache.get('repo', s:_r.repo.get, [':'], {})
     return s:_r.cache.get('cs', repo.functions.getwork, [repo], {})
 endfunction
 let s:_functions+=['aurum#changeset']
+"▶1 filestatus
+function s:F.filestatus(status)
+    return get(keys(filter(copy(a:status), '!empty(v:val)')), 0, '')
+endfunction
 "▶1 aurum#status
 function aurum#status(...)
     if !empty(&buftype)
         return ''
     endif
+    let buf=bufnr('%')
+    if has_key(s:abuffers, buf) && has_key(s:abuffers[buf], 'status')
+        return s:F.filestatus(s:F.aget(s:abuffers[buf].status, 0))
+    endif
     let [cbvar, repo, file]=s:F.getcrf()
     if repo is 0 || file is 0
         return ''
     endif
+    if has_key(repo.functions, 'astatus')
+        augroup AuInvalidateStatusCache
+            autocmd! BufWritePost <buffer>
+                        \ :call s:F.aget(s:abuffers[expand('<abuf>')].status, 1)
+        augroup END
+        return s:F.filestatus(s:F.anew(buf, repo, 'status', 'status',
+                    \                  0, 0, [file], 1, 1))
+    endif
     augroup AuInvalidateStatusCache
         autocmd! BufWritePost <buffer> :call s:_r.cache.del('status')
     augroup END
-    return get(keys(filter(copy(s:_r.cache.get('status', repo.functions.status,
-                \                              [repo, 0, 0, [file], 1, 1], {})),
-                \          'index(v:val, file)!=-1')), 0, '')
+    return s:F.filestatus(s:_r.cache.get('status', repo.functions.status,
+                \                        [repo, 0, 0, [file], 1, 1], {}))
 endfunction
 let s:_functions+=['aurum#status']
 let s:_augroups+=['AuInvalidateStatusCache']
 "▶1 aurum#branch
 function aurum#branch(...)
+    let buf=bufnr('%')
+    if has_key(s:abuffers, buf) && has_key(s:abuffers[buf], 'branch')
+        return s:F.aget(s:abuffers[buf].branch, 0)
+    endif
     let repo=((a:0)?(a:1):(aurum#repository()))
     if empty(repo)
         return ''
     endif
+    if has_key(repo.functions, 'agetrepoprop')
+        return s:F.anew(buf, repo, 'branch', 'getrepoprop', 'branch')
+    endif
     return s:_r.cache.get('branch', repo.functions.getrepoprop,
                 \         [repo, 'branch'], {})
 endfunction
 let s:_functions+=['aurum#branch']
 "▶1
-call frawor#Lockvar(s:, '_pluginloaded,_r')
+call frawor#Lockvar(s:, '_r,abuffers,akeys')
 " vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File autoload/aurum/drivers/mercurial.vim

 " type :: "modified" | "added" | "removed" | "deleted" | "unknown" | "ignored"
 "       | "clean"
 if s:usepythondriver "▶2
+let s:revargsexpr='v:val is 0? '.
+                \       '"None":'.
+                \ 'v:key>=3?'.
+                \       '(empty(v:val)?"False":"True"):'.
+                \       '"vim.eval(''a:".(v:key+1)."'')"'
 function s:hg.status(repo, ...)
-    let revargs=join(map(copy(a:000), 'v:val is 0? '.
-                \                           '"None":'.
-                \                     '(v:key>3 && v:val is 1)?'.
-                \                           '"True":'.
-                \                           '"vim.eval(''a:".(v:key+1)."'')"'),
-                \    ',')
+    let revargs=join(map(copy(a:000), s:revargsexpr), ',')
     let d={}
     try
         execute s:pya.'get_status(vim.eval("a:repo.path"), '.revargs.')'
     endif
     return cs
 endfunction
+"▶1 astatus, agetcs, agetrepoprop
+if s:_r.repo.userepeatedcmd
+    if s:usepythondriver
+        function s:hg.astatus(repo, interval, ...)
+            let args = string(a:interval).', '.s:pp.'._get_status, '.
+                        \'vim.eval("a:repo.path"), '
+            let args.= join(map(copy(a:000), s:revargsexpr), ',')
+            return pyeval('aurum.repeatedcmd.new('.args.')')
+        endfunction
+        function s:hg.agetcs(repo, interval, rev)
+            return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                        \                        s:pp.'._get_cs, '.
+                        \                       'vim.eval("a:repo.path"), '.
+                        \                       'vim.eval("a:rev"))')
+        endfunction
+        function s:hg.agetrepoprop(repo, interval, prop)
+            return pyeval('aurum.repeatedcmd.new('.string(a:interval).', '.
+                        \                        s:pp.'.get_one_prop, '.
+                        \                       'vim.eval("a:repo.path"), '.
+                        \                       'vim.eval("a:prop"))')
+        endfunction
+    endif
+endif
 "▶1 hg.svnrev :: repo, rev → svnrev
 function s:hg.svnrev(repo, rev)
     let lines=s:F.hg(a:repo, 'svn', ['info'], {'rev': ''.a:rev}, 0, 'svn')
 "▶1 hg.gitrev :: repo, rev → githex
 if s:usepythondriver
 function s:hg.githex(repo, rev)
-    execute s:pya.'git_hash(vim.eval("a:repo.path"), vim.eval("a:rev"))'
+    let d={}
+    try
+        execute s:pya.'git_hash(vim.eval("a:repo.path"), vim.eval("a:rev"))'
+    endtry
+    return d.git_hex
 endfunction
 endif
 "▶1 Register driver

File autoload/aurum/repo.vim

 "▶1
-execute frawor#Setup('5.4', {'@/resources': '0.0',
+execute frawor#Setup('5.5', {'@/resources': '0.0',
             \                       '@/os': '0.0',
             \                  '@/options': '0.0',
             \          '@%aurum/lineutils': '0.0',
 function s:deffuncs.checkremote(...)
     return 0
 endfunction
-"▶1 aget :: _, rcid + python -> ?
+"▶1 aget, aremove
 let s:userepeatedcmd=0
 if has('python') && exists('*pyeval')
     try
         python import aurum.repeatedcmd
+        python reload(aurum.repeatedcmd)
         let s:userepeatedcmd=1
     catch
     endtry
 endif
 if s:userepeatedcmd
-    function s:deffuncs.aget(repo, rcid)
-        return pyeval('aurum.repeatedcmd.get(vim.bindeval("a:rcid"))')
+    augroup AuRCFinish
+        autocmd! VimLeave * python aurum.repeatedcmd.finish()
+    augroup END
+    let s:_augroups+=['AuRCFinish']
+    function s:deffuncs.aget(repo, rcid, ...)
+        return pyeval('aurum.repeatedcmd.get(vim.bindeval("a:rcid"), '.
+                    \                        (a:0 && a:1? 'True': 'False').')')
+    endfunction
+    function s:deffuncs.apause(repo, rcid)
+        python aurum.repeatedcmd.pause(vim.bindeval("a:rcid"))
+    endfunction
+    function s:deffuncs.aresume(repo, rcid)
+        python aurum.repeatedcmd.resume(vim.bindeval("a:rcid"))
     endfunction
     function s:deffuncs.aremove(repo, rcid)
-        return pyeval('aurum.repeatedcmd.remove(vim.bindeval("a:rcid"))')
+        python aurum.repeatedcmd.remove(vim.bindeval("a:rcid"))
     endfunction
 endif
 "▶1 iterfuncs: cs generators
     return repo
 endfunction
 "▶1 Post resource
-call s:_f.postresource('repo', {'get': s:F.getrepo,})
+call s:_f.postresource('repo', {'get': s:F.getrepo,
+            \        'userepeatedcmd': s:userepeatedcmd})
 "▶1 regdriver feature
 let s:requiredfuncs=['repo', 'getcs', 'checkdir']
 let s:optfuncs=['readfile', 'annotate', 'diff', 'status', 'commit', 'update',
             \   'diffre', 'getrepoprop', 'forget', 'branch', 'label',
-            \   'push', 'pull', 'astatus', 'agetcs', 'agetrepoprop', 'aget',
-            \   'aremove']
+            \   'push', 'pull']
 "▶2 regdriver :: {f}, name, funcs → + s:drivers
 function s:F.regdriver(plugdict, fdict, name, funcs)
     "▶3 Check arguments

File doc/aurum.txt

         function s:driver.getroot(path)
             return a:path
         endfunction
-<                                                           *aurum-rf-astatus*
+<                                                          *aurum-repeatedcmd*
+The following functions accept repo argument, but may be absent (dummy ones 
+throwing nimp exception are not created):
+                                                            *aurum-rf-astatus*
   astatus :: interval, [hex[, hex[, [ file ][, clean[, ign]]]]] -> rcid
   agetcs :: interval, rev -> rcid                            *aurum-rf-agetcs*
   agetrepoprop :: interval, propname -> rcid           *aurum-rf-agetrepoprop*
     replacements). Return an ID. Use |aurum-rf-aget| to get actual aurum-rf-* 
     output. All currently existing implementations require python-2* with 
     working threading module.
-  aget :: rcid -> ?                                            *aurum-rf-aget*
-    Output current value of repeatedly run function.
+  aget :: rcid[, now] -> ?                                     *aurum-rf-aget*
+    Output current value of repeatedly run function. With “now” it is not 
+    using cache.
   aremove :: rcid                                           *aurum-rf-aremove*
     Stop repeating functions.
+  apause :: rcid                                             *aurum-rf-apause*
+  aresume :: rcid                                           *aurum-rf-aresume*
+    Pause and resume execution (i.e. process is not deleted like with aremove, 
+    but it only sleeps in paused state).
 
 ------------------------------------------------------------------------------
 8.2. Changeset                                               *aurum-changeset*
                          "ancestors": Iterates recursively over 
                                       |aurum-cs.parents|, parents of the 
                                       parents and so on.
+  |aurum-rf-aget|          Uses aurum.repeatedcmd.get.
+  |aurum-rf-aremove|       Uses aurum.repeatedcmd.remove.
+  |aurum-rf-apause|        Uses aurum.repeatedcmd.pause.
+  |aurum-rf-aresume|       Uses aurum.repeatedcmd.resume.
 Other functions, mentioned in |aurum-repo.functions|, but not mentioned here, 
-throw an "nimp" exception.
+throw an "nimp" exception (except for |aurum-repeatedcmd| functions, they may 
+be absent, but they never throw "nimp").
 
 ==============================================================================
 10. Notes about various driver implementations            *aurum-driver-notes*
     5.1: Added repo.hasphases.
     5.2: Added repo.revreg.
     5.3: Added {ign} argument to |aurum-rf-status|.
+    5.4: Added |aurum-rf-astatus|, |aurum-rf-agetcs|, |aurum-rf-agetrepoprop|, 
+         |aurum-rf-aget| and |aurum-rf-aremove|.
+    5.5: Added |aurum-rf-apause| and |aurum-rf-aresume|.
 @aurum:
     0.1: Added |:AuBranch| and |:AuName|.
     0.2: Added |:AuOther|.
     1.0: Added _r.getcrf(), removed cachebvars export.
     2.0: Removed getcrf, added _r.cache.getcbvar.
     2.1: Added _r.cache.wipe().
+    2.2: Added _r.cache.getinterval().
 ftplugin/aurumlog:
     0.1: Added OpenAny and AnnotateAny mappings.
 @%aurum/drivers/common/hypsites:

File plugin/aurum/cache.vim

 "▶1
 scriptencoding utf-8
 if !exists('s:_pluginloaded')
-    execute frawor#Setup('2.1', {'@/resources': '0.0',
+    execute frawor#Setup('2.2', {'@/resources': '0.0',
                 \                  '@/options': '0.0',}, 0)
     finish
 elseif s:_pluginloaded
     call map(copy(s:cachebvars), 'has_key(v:val,a:key) && '.
                 \                                  'empty(remove(v:val,a:key))')
 endfunction
+"▶1 r.getinterval
+function s:r.getinterval(key)
+    return s:_f.getoption(a:key.'cachetime')
+endfunction
 "▶1 Post cache resource
 call s:_f.postresource('cache', s:r)
 "▶1

File python/aurum/aumercurial.py

         try:
             repo=g_repo(path)
             try:
-                func(repo, *args, **kwargs)
+                return func(repo, *args, **kwargs)
             finally:
                 repo.ui.flush()
         except AurumError:
             pass
     return f
 
+def autoexportmethod(*extargs, **extkwargs):
+    def autoexportmethoddec(func):
+        def f2(path, *args, **kwargs):
+            return func(g_repo(path), *args, **kwargs)
+        globals()['_'+func.__name__]=f2
+        def f(*args, **kwargs):
+            vim_extend(val=func(*args, **kwargs), *extargs, **extkwargs)
+        return f
+    return autoexportmethoddec
+
 def nonutf_dumps(obj):
     todump=[('dump', obj)]
     r=''
         for a in args:
             self._colinfo=echoe(a, self._colinfo)
 
+    def flush(self):
+        pass
+
 class CaptureUI(PrintUI):
     def __init__(self):
         self._captured=[]
     return r
 
 @outermethod
+@autoexportmethod()
 def get_tags(repo):
     tags=get_cs_tag_dict([(repo[val].hex(), key) for key, val
                           in repo.tags().items()])
     if hasattr(repo, 'listkeys'):
         bookmarks=get_cs_tag_dict([(val, key) for key, val
                                    in repo.listkeys('bookmarks').items()])
-    vim_extend(val={'tags': tags, 'bookmarks': bookmarks})
+    return {'tags': tags, 'bookmarks': bookmarks}
 
 @outermethod
+@autoexportmethod()
 def get_phases(repo):
-    vim_extend(val={'phasemap': dict((lambda cs: (cs.hex(), cs.phasestr()))(repo[rev])
-                                                 for rev in repo)})
+    return {'phasemap': dict((lambda cs: (cs.hex(), cs.phasestr()))(repo[rev])
+                              for rev in repo)}
 
 @outermethod
+@autoexportmethod(var='cs')
 def get_cs(repo, rev):
     cs=g_cs(repo, rev)
-    vim_extend(var='cs', val=set_rev_dict(cs, {'rev': cs.rev()}))
+    return set_rev_dict(cs, {'rev': cs.rev()})
 
 @outermethod
+@autoexportmethod(var='repo')
 def new_repo(repo):
     # TODO remove bookmark label type if it is not available
     vim_repo={'has_octopus_merges': 0,
         vim_repo['labeltypes'].pop()
         vim_repo['updkeys'].pop()
         vim_repo['hasbookmarks']=0
-    vim_extend(var='repo', val=vim_repo)
+    return vim_repo
 
 @outermethod
+@autoexportmethod(var='r', utf=False, list=True)
 def get_file(repo, rev, filepath):
     fctx=g_fctx(g_cs(repo, rev), filepath)
-    lines=[line.replace("\0", "\n") for line in fctx.data().split("\n")]
-    vim_extend(var='r', val=lines, utf=False, list=True)
+    return [line.replace("\0", "\n") for line in fctx.data().split("\n")]
 
 @outermethod
+@autoexportmethod(var='r', utf=False, list=True)
 def annotate(repo, rev, filepath):
     ann=g_fctx(g_cs(repo, rev), filepath).annotate(follow=True, linenumber=True)
-    ann_vim=[(line[0][0].path(), str(line[0][0].rev()), line[0][1])
-                                                            for line in ann]
-    vim_extend(var='r', val=ann_vim, utf=False, list=True)
+    return [(line[0][0].path(), str(line[0][0].rev()), line[0][1])
+                                                                for line in ann]
 
 def run_in_dir(dir, func, *args, **kwargs):
     workdir=os.path.abspath('.')
     run_in_dir(repo.root, commands.diff, *args, **kwargs)
 
 @outermethod
+@autoexportmethod(var='r', utf=False, list=True)
 def diff(*args, **kwargs):
     ui=CaptureUI()
     dodiff(ui, *args, **kwargs)
-    vim_extend(var='r', val=ui._getCaptured(), utf=False, list=True)
+    return ui._getCaptured()
 
 @outermethod
 def diffToBuffer(*args, **kwargs):
         else:
             copies_vim[f]=0
             renames_vim[f]=0
-    vim_extend(var='a:cs', val={'renames': renames_vim, 'copies': copies_vim},
-               utf=False)
+    return {'renames': renames_vim, 'copies': copies_vim}
 
 @outermethod
+@autoexportmethod(var='a:cs', utf=False)
 def get_cs_prop(repo, rev, prop):
     cs=g_cs(repo, rev)
     if prop=='files' or prop=='removes' or prop=='changes':
                 am.append(f)
             else:
                 r.append(f)
-        vim_extend(var='a:cs', val={'files': am, 'removes': r, 'changes': c},
-                   utf=False)
-        return
+        return {'files': am, 'removes': r, 'changes': c}
     elif prop=='renames' or prop=='copies':
-        get_renames(cs)
-        return
+        return get_renames(cs)
     elif prop=='allfiles':
         r=[f for f in cs]
     elif prop=='children':
         r=cs.__getattribute__(prop)()
     # XXX There is much code relying on the fact that after getcsprop
     #     property with given name is added to changeset dictionary
-    vim_extend(var='a:cs', val={prop : r}, utf=False)
+    return {prop : r}
 
 @outermethod
+@autoexportmethod(utf=False)
 def get_status(repo, rev1=None, rev2=None, files=None, clean=None, ignored=None):
     if rev1 is None and rev2 is None:
         rev1='.'
             m=match.match(None, None, files, exact=True)
         status=repo.status(rev1, rev2, match=m, clean=clean, ignored=ignored,
                            unknown=True)
-        vim_extend(val={'modified': status[0],
-                           'added': status[1],
-                         'removed': status[2],
-                         'deleted': status[3],
-                         'unknown': status[4],
-                         'ignored': status[5],
-                           'clean': status[6],},
-                   utf=False)
+        return {'modified': status[0],
+                   'added': status[1],
+                 'removed': status[2],
+                 'deleted': status[3],
+                 'unknown': status[4],
+                 'ignored': status[5],
+                   'clean': status[6],}
     else:
         vim_throw('statuns', repo.path)
 
               'branch': lambda repo: repo.dirstate.branch(),
         }
 @outermethod
+@autoexportmethod()
 def get_repo_prop(repo, prop):
     if prop in repo_props:
         r=repo_props[prop](repo)
         if r is None:
             vim_throw('failcfg', prop, repo.path)
         else:
-            vim_extend(val={prop : r})
+            return {prop : r}
     else:
         vim_throw('nocfg', repo.path, prop)
 
+def get_one_prop(path, prop):
+    return _get_repo_prop(path, prop)[prop]
+
 @outermethod
 def call_cmd(repo, attr, bkwargs, *args, **kwargs):
     if bkwargs:
                *cargs, **kwargs)
 
 @outermethod
+@autoexportmethod(var='r', utf=False, list=True)
 def grep(repo, pattern, files, revisions=None, ignore_case=False, wdfiles=True):
     ui=CaptureUI()
     args=[ui, repo, pattern]
         else:
             file=(rev, file)
         r_vim.append({'filename': file, 'lnum': int(lnum), 'text': text})
-    vim_extend(var='r', val=r_vim, utf=False, list=True)
+    return r_vim
 
 @outermethod
+@autoexportmethod()
 def git_hash(repo, rev):
     hggitpath=None
     hggitname=None
         r=git.map_git_get(cs.hex())
         if r is None:
             vim_throw('nogitrev', cs.hex(), repo.path)
-        vim.command('return '+json.dumps(r))
+        return {'git_hex': r}
     finally:
         sys.path.pop(0)
 

File python/aurum/repeatedcmd.py

-from multiprocessing import Process, Queue, Value
 from aurum.utils import readsystem
 import time
 
+# Threads do not run in vim on ARM linux. Have to use processes instead.
+use_threads=False
+
+if use_threads:
+    from multiprocessing import Value
+    from Queue import Queue
+    from threading import Lock
+    from threading import Thread as Process
+else:
+    from multiprocessing import Process, Queue, Value, Lock
+    from signal import signal, SIGTERM, SIG_DFL
+
 class ProcessHaltedError(Exception):
     pass
 
 class RepeatedCmd(object):
-    __slots__ = set(('interval', 'func', 'timer', 'value'))
+    __slots__ = set(('interval', 'queue', 'lock', 'func', 'process', 'value',
+                     'args', 'kwargs', 'paused'))
     def __init__(self, interval, func, *args, **kwargs):
         self.interval = Value('f', float(interval))
         self.queue    = Queue()
+        self.lock     = Lock()
         self.func     = func
-        def procfunc(queue, interval, func, args, kwargs):
-            while True:
-                value = func(*args, **kwargs)
-                while(not queue.empty()):
-                    queue.get()
-                queue.put(value)
-                time.sleep(interval.value)
+        self.args     = args
+        self.kwargs   = kwargs
+        self.paused   = Value('b', 0)
+
+        def procfunc(queue, lock, interval, func, args, kwargs):
+            # Override default signal vim handler with system default behavior 
+            # (i.e. just terminate). Without this hack process.terminate() may 
+            # leave the process alive. Bad, I do not know why it leaves it alive 
+            # only in some limited set of cases.
+            signal(SIGTERM, SIG_DFL)
+            while interval.value > 0:
+                starttime = time.clock()
+                if not self.paused.value:
+                    value     = func(*args, **kwargs)
+                    lock.acquire()
+                    # If I only knew the size of func() output I would have used 
+                    # Value()
+                    while(not queue.empty()):
+                        queue.get()
+                    queue.put(value)
+                    lock.release()
+                try:
+                    time.sleep(interval.value-(time.clock()-starttime))
+                except Exception:
+                    # Work when interval-elapsed results in negative value.
+                    pass
+
         self.process  = Process(target=procfunc,
-                                args=(self.queue, self.interval, func, args, kwargs))
+                                args=(self.queue, self.lock, self.interval,
+                                      func, args, kwargs))
         self.process.start()
         self.value = None
 
     def setinterval(self, interval):
-        if not self.process.is_alive():
+        if not self.alive():
             raise ProcessHaltedError('Child process is not alive')
         self.interval.value = float(interval)
 
     def getvalue(self):
-        if not self.process.is_alive():
+        if not self.alive():
             raise ProcessHaltedError('Child process is not alive')
+        self.lock.acquire()
         if self.value is None or not self.queue.empty():
             while(not self.queue.empty()):
                 self.value = self.queue.get()
+        self.lock.release()
         return self.value
 
     def getcurrentvalue(self):
-        self.value = self.func()
+        self.value = self.func(*self.args, **self.kwargs)
         return self.value
 
+    def alive(self):
+        return self.process.is_alive()
+
     def stop(self):
-        if not self.process.is_alive():
+        if not self.alive():
             raise ProcessHaltedError('Child process is not alive')
+        # For some reason process.terminate() sometimes may not seem to work. 
+        # This is a workaround. Seems it is no longer needed with 
+        # self.process.join(), just keeping it here
+        self.interval.value = -1.0
         self.process.terminate()
+        # Anti-zombie code
+        self.process.join()
 
-    __del__ = stop
+    def pause(self):
+        self.paused.value = 1
+
+    def resume(self):
+        self.paused.value = 0
+
+    def __del__(self):
+        if self.alive():
+            return self.stop()
 
 processes={}
 
 thcount=0
 
 def new(*args, **kwargs):
+    global processes
     global thcount
-    global processes
     rcid=thcount
     thcount+=1
     processes[rcid]=RepeatedCmd(*args, **kwargs)
     return rcid
 
-def get(rcid):
+def get(rcid, now=False):
     global processes
-    return processes[rcid].getvalue()
+    if now:
+        return processes[rcid].getcurrentvalue()
+    else:
+        return processes[rcid].getvalue()
 
 def remove(rcid):
     global processes
-    thread=processes.pop(rcid)
-    thread.stop()
+    process=processes.pop(rcid)
+    process.stop()
+
+def finish():
+    global processes
+    for process in processes.values():
+        process.stop()
+    processes={}
+
+def pause(rcid):
+    global processes
+    processes[rcid].pause()
+
+def resume(rcid):
+    global processes
+    processes[rcid].resume()