Establish stream before `fsp_compute` so that backfill updates work again..
							parent
							
								
									3a3baca9bc
								
							
						
					
					
						commit
						612813e937
					
				| 
						 | 
					@ -76,7 +76,6 @@ async def filter_quotes_by_sym(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def fsp_compute(
 | 
					async def fsp_compute(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ctx: tractor.Context,
 | 
					 | 
				
			||||||
    symbol: Symbol,
 | 
					    symbol: Symbol,
 | 
				
			||||||
    feed: Feed,
 | 
					    feed: Feed,
 | 
				
			||||||
    quote_stream: trio.abc.ReceiveChannel,
 | 
					    quote_stream: trio.abc.ReceiveChannel,
 | 
				
			||||||
| 
						 | 
					@ -86,7 +85,7 @@ async def fsp_compute(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    func: Callable,
 | 
					    func: Callable,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    attach_stream: bool = False,
 | 
					    # attach_stream: bool = False,
 | 
				
			||||||
    task_status: TaskStatus[None] = trio.TASK_STATUS_IGNORED,
 | 
					    task_status: TaskStatus[None] = trio.TASK_STATUS_IGNORED,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
| 
						 | 
					@ -193,46 +192,47 @@ async def fsp_compute(
 | 
				
			||||||
    profiler(f'{func_name} pushed history')
 | 
					    profiler(f'{func_name} pushed history')
 | 
				
			||||||
    profiler.finish()
 | 
					    profiler.finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: UGH, what is the right way to do something like this?
 | 
					 | 
				
			||||||
    if not ctx._started_called:
 | 
					 | 
				
			||||||
        await ctx.started(index)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # setup a respawn handle
 | 
					    # setup a respawn handle
 | 
				
			||||||
    with trio.CancelScope() as cs:
 | 
					    with trio.CancelScope() as cs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO: might be better to just make a "restart" method where
 | 
				
			||||||
 | 
					        # the target task is spawned implicitly and then the event is
 | 
				
			||||||
 | 
					        # set via some higher level api? At that poing we might as well
 | 
				
			||||||
 | 
					        # be writing a one-cancels-one nursery though right?
 | 
				
			||||||
        tracker = TaskTracker(trio.Event(), cs)
 | 
					        tracker = TaskTracker(trio.Event(), cs)
 | 
				
			||||||
        task_status.started((tracker, index))
 | 
					        task_status.started((tracker, index))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        profiler(f'{func_name} yield last index')
 | 
					        profiler(f'{func_name} yield last index')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # import time
 | 
					        # import time
 | 
				
			||||||
        # last = time.time()
 | 
					        # last = time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            # rt stream
 | 
					 | 
				
			||||||
            async with ctx.open_stream() as stream:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # always trigger UI refresh after history update,
 | 
					            async for processed in out_stream:
 | 
				
			||||||
                # see ``piker.ui._fsp.FspAdmin.open_chain()`` and
 | 
					 | 
				
			||||||
                # ``piker.ui._display.trigger_update()``.
 | 
					 | 
				
			||||||
                await stream.send('update')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                async for processed in out_stream:
 | 
					                log.debug(f"{func_name}: {processed}")
 | 
				
			||||||
 | 
					                key, output = processed
 | 
				
			||||||
 | 
					                index = src.index
 | 
				
			||||||
 | 
					                dst.array[-1][key] = output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    log.debug(f"{func_name}: {processed}")
 | 
					                # NOTE: for now we aren't streaming this to the consumer
 | 
				
			||||||
                    key, output = processed
 | 
					                # stream latest array index entry which basically just acts
 | 
				
			||||||
                    index = src.index
 | 
					                # as trigger msg to tell the consumer to read from shm
 | 
				
			||||||
                    dst.array[-1][key] = output
 | 
					                # TODO: further this should likely be implemented much
 | 
				
			||||||
 | 
					                # like our `Feed` api where there is one background
 | 
				
			||||||
 | 
					                # "service" task which computes output and then sends to
 | 
				
			||||||
 | 
					                # N-consumers who subscribe for the real-time output,
 | 
				
			||||||
 | 
					                # which we'll likely want to implement using local-mem
 | 
				
			||||||
 | 
					                # chans for the fan out?
 | 
				
			||||||
 | 
					                # if attach_stream:
 | 
				
			||||||
 | 
					                #     await client_stream.send(index)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # NOTE: for now we aren't streaming this to the consumer
 | 
					                # period = time.time() - last
 | 
				
			||||||
                    # stream latest array index entry which basically just acts
 | 
					                # hz = 1/period if period else float('nan')
 | 
				
			||||||
                    # as trigger msg to tell the consumer to read from shm
 | 
					                # if hz > 60:
 | 
				
			||||||
                    if attach_stream:
 | 
					                #     log.info(f'FSP quote too fast: {hz}')
 | 
				
			||||||
                        await stream.send(index)
 | 
					                # last = time.time()
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    # period = time.time() - last
 | 
					 | 
				
			||||||
                    # hz = 1/period if period else float('nan')
 | 
					 | 
				
			||||||
                    # if hz > 60:
 | 
					 | 
				
			||||||
                    #     log.info(f'FSP quote too fast: {hz}')
 | 
					 | 
				
			||||||
                    # last = time.time()
 | 
					 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            tracker.complete.set()
 | 
					            tracker.complete.set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -323,7 +323,6 @@ async def cascade(
 | 
				
			||||||
            fsp_target = partial(
 | 
					            fsp_target = partial(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                fsp_compute,
 | 
					                fsp_compute,
 | 
				
			||||||
                ctx=ctx,
 | 
					 | 
				
			||||||
                symbol=symbol,
 | 
					                symbol=symbol,
 | 
				
			||||||
                feed=feed,
 | 
					                feed=feed,
 | 
				
			||||||
                quote_stream=quote_stream,
 | 
					                quote_stream=quote_stream,
 | 
				
			||||||
| 
						 | 
					@ -332,7 +331,7 @@ async def cascade(
 | 
				
			||||||
                src=src,
 | 
					                src=src,
 | 
				
			||||||
                dst=dst,
 | 
					                dst=dst,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # func_name=func_name,
 | 
					                # target
 | 
				
			||||||
                func=func
 | 
					                func=func
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -344,90 +343,113 @@ async def cascade(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            profiler(f'{func_name}: fsp up')
 | 
					            profiler(f'{func_name}: fsp up')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            async def resync(tracker: TaskTracker) -> tuple[TaskTracker, int]:
 | 
					            # sync client
 | 
				
			||||||
                # TODO: adopt an incremental update engine/approach
 | 
					            await ctx.started(index)
 | 
				
			||||||
                # where possible here eventually!
 | 
					 | 
				
			||||||
                log.warning(f're-syncing fsp {func_name} to source')
 | 
					 | 
				
			||||||
                tracker.cs.cancel()
 | 
					 | 
				
			||||||
                await tracker.complete.wait()
 | 
					 | 
				
			||||||
                return await n.start(fsp_target)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def is_synced(
 | 
					            # XXX:  rt stream with client which we MUST
 | 
				
			||||||
                src: ShmArray,
 | 
					            # open here (and keep it open) in order to make
 | 
				
			||||||
                dst: ShmArray
 | 
					            # incremental "updates" as history prepends take
 | 
				
			||||||
            ) -> tuple[bool, int, int]:
 | 
					            # place.
 | 
				
			||||||
                '''Predicate to dertmine if a destination FSP
 | 
					            async with ctx.open_stream() as client_stream:
 | 
				
			||||||
                output array is aligned to its source array.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                '''
 | 
					                # TODO: these likely should all become
 | 
				
			||||||
                step_diff = src.index - dst.index
 | 
					                # methods of this ``TaskLifetime`` or wtv
 | 
				
			||||||
                len_diff = abs(len(src.array) - len(dst.array))
 | 
					                # abstraction..
 | 
				
			||||||
                return not (
 | 
					                async def resync(
 | 
				
			||||||
                    # the source is likely backfilling and we must
 | 
					                    tracker: TaskTracker,
 | 
				
			||||||
                    # sync history calculations
 | 
					 | 
				
			||||||
                    len_diff > 2 or
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # we aren't step synced to the source and may be
 | 
					                ) -> tuple[TaskTracker, int]:
 | 
				
			||||||
                    # leading/lagging by a step
 | 
					                    # TODO: adopt an incremental update engine/approach
 | 
				
			||||||
                    step_diff > 1 or
 | 
					                    # where possible here eventually!
 | 
				
			||||||
                    step_diff < 0
 | 
					                    log.warning(f're-syncing fsp {func_name} to source')
 | 
				
			||||||
                ), step_diff, len_diff
 | 
					                    tracker.cs.cancel()
 | 
				
			||||||
 | 
					                    await tracker.complete.wait()
 | 
				
			||||||
 | 
					                    tracker, index = await n.start(fsp_target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            async def poll_and_sync_to_step(
 | 
					                    # always trigger UI refresh after history update,
 | 
				
			||||||
 | 
					                    # see ``piker.ui._fsp.FspAdmin.open_chain()`` and
 | 
				
			||||||
 | 
					                    # ``piker.ui._display.trigger_update()``.
 | 
				
			||||||
 | 
					                    await client_stream.send('update')
 | 
				
			||||||
 | 
					                    return tracker, index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                tracker: TaskTracker,
 | 
					                def is_synced(
 | 
				
			||||||
                src: ShmArray,
 | 
					                    src: ShmArray,
 | 
				
			||||||
                dst: ShmArray,
 | 
					                    dst: ShmArray
 | 
				
			||||||
 | 
					                ) -> tuple[bool, int, int]:
 | 
				
			||||||
 | 
					                    '''Predicate to dertmine if a destination FSP
 | 
				
			||||||
 | 
					                    output array is aligned to its source array.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ) -> tuple[TaskTracker, int]:
 | 
					                    '''
 | 
				
			||||||
 | 
					                    step_diff = src.index - dst.index
 | 
				
			||||||
 | 
					                    len_diff = abs(len(src.array) - len(dst.array))
 | 
				
			||||||
 | 
					                    return not (
 | 
				
			||||||
 | 
					                        # the source is likely backfilling and we must
 | 
				
			||||||
 | 
					                        # sync history calculations
 | 
				
			||||||
 | 
					                        len_diff > 2 or
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        # we aren't step synced to the source and may be
 | 
				
			||||||
 | 
					                        # leading/lagging by a step
 | 
				
			||||||
 | 
					                        step_diff > 1 or
 | 
				
			||||||
 | 
					                        step_diff < 0
 | 
				
			||||||
 | 
					                    ), step_diff, len_diff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                async def poll_and_sync_to_step(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    tracker: TaskTracker,
 | 
				
			||||||
 | 
					                    src: ShmArray,
 | 
				
			||||||
 | 
					                    dst: ShmArray,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ) -> tuple[TaskTracker, int]:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                synced, step_diff, _ = is_synced(src, dst)
 | 
					 | 
				
			||||||
                while not synced:
 | 
					 | 
				
			||||||
                    tracker, index = await resync(tracker)
 | 
					 | 
				
			||||||
                    synced, step_diff, _ = is_synced(src, dst)
 | 
					                    synced, step_diff, _ = is_synced(src, dst)
 | 
				
			||||||
 | 
					                    while not synced:
 | 
				
			||||||
 | 
					                        tracker, index = await resync(tracker)
 | 
				
			||||||
 | 
					                        synced, step_diff, _ = is_synced(src, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return tracker, step_diff
 | 
					                    return tracker, step_diff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            s, step, ld = is_synced(src, dst)
 | 
					                s, step, ld = is_synced(src, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # detect sample period step for subscription to increment
 | 
					                # detect sample period step for subscription to increment
 | 
				
			||||||
            # signal
 | 
					                # signal
 | 
				
			||||||
            times = src.array['time']
 | 
					                times = src.array['time']
 | 
				
			||||||
            delay_s = times[-1] - times[times != times[-1]][-1]
 | 
					                delay_s = times[-1] - times[times != times[-1]][-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Increment the underlying shared memory buffer on every
 | 
					                # Increment the underlying shared memory buffer on every
 | 
				
			||||||
            # "increment" msg received from the underlying data feed.
 | 
					                # "increment" msg received from the underlying data feed.
 | 
				
			||||||
            async with feed.index_stream(int(delay_s)) as istream:
 | 
					                async with feed.index_stream(
 | 
				
			||||||
 | 
					                    int(delay_s)
 | 
				
			||||||
 | 
					                ) as istream:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                profiler(f'{func_name}: sample stream up')
 | 
					                    profiler(f'{func_name}: sample stream up')
 | 
				
			||||||
                profiler.finish()
 | 
					                    profiler.finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                async for _ in istream:
 | 
					                    async for _ in istream:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # respawn the compute task if the source
 | 
					                        # respawn the compute task if the source
 | 
				
			||||||
                    # array has been updated such that we compute
 | 
					                        # array has been updated such that we compute
 | 
				
			||||||
                    # new history from the (prepended) source.
 | 
					                        # new history from the (prepended) source.
 | 
				
			||||||
                    synced, step_diff, _ = is_synced(src, dst)
 | 
					                        synced, step_diff, _ = is_synced(src, dst)
 | 
				
			||||||
                    if not synced:
 | 
					                        if not synced:
 | 
				
			||||||
                        tracker, step_diff = await poll_and_sync_to_step(
 | 
					                            tracker, step_diff = await poll_and_sync_to_step(
 | 
				
			||||||
                            tracker,
 | 
					                                tracker,
 | 
				
			||||||
                            src,
 | 
					                                src,
 | 
				
			||||||
                            dst,
 | 
					                                dst,
 | 
				
			||||||
                        )
 | 
					                            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        # skip adding a last bar since we should already
 | 
					                            # skip adding a last bar since we should already
 | 
				
			||||||
                        # be step alinged
 | 
					                            # be step alinged
 | 
				
			||||||
                        if step_diff == 0:
 | 
					                            if step_diff == 0:
 | 
				
			||||||
                            continue
 | 
					                                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # read out last shm row, copy and write new row
 | 
					                        # read out last shm row, copy and write new row
 | 
				
			||||||
                    array = dst.array
 | 
					                        array = dst.array
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # some metrics like vlm should be reset
 | 
					                        # some metrics like vlm should be reset
 | 
				
			||||||
                    # to zero every step.
 | 
					                        # to zero every step.
 | 
				
			||||||
                    if zero_on_step:
 | 
					                        if zero_on_step:
 | 
				
			||||||
                        last = zeroed
 | 
					                            last = zeroed
 | 
				
			||||||
                    else:
 | 
					                        else:
 | 
				
			||||||
                        last = array[-1:].copy()
 | 
					                            last = array[-1:].copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    dst.push(last)
 | 
					                        dst.push(last)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue