~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
186 lines · 5.2 KB Raw History
package requester

import (
	"context"
	"encoding/json"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/market"
)

const (
	MarketInboxRetention = 10 * time.Minute

	inboxPrunedLogEvery = 100
)

type inboxVerdict int

const (
	inboxKeep inboxVerdict = iota

	inboxForeign

	inboxUnknown

	inboxEnded
)

func (q *Requester) Synced() {
	if since, ok := q.sinceStartMs(); !q.synced.Swap(true) && ok {
		q.log.Info("requester_start_timing", "step", "synced", "sinceStartMs", since)
	}
	q.startOrphanRevokes()
	q.staleSweep.Do(func() {
		q.wg.Go(q.releaseStaleTransient)
	})
}

func (q *Requester) addressees(skip func(v1alpha1.ComputeContract) bool) *market.Addressees {
	a := market.RequesterAddresseesOf(q.contracts.List(), skip)
	q.mu.Lock()
	runners := make([]*contractRunner, 0, len(q.runners))
	for _, runner := range q.runners {
		runners = append(runners, runner)
	}
	q.mu.Unlock()
	for _, runner := range runners {
		contract, ok := q.contracts.Get(runner.name)
		if !ok || (skip != nil && skip(contract)) {
			continue
		}
		refs := market.RefsOfContract(runner.stateCopy())
		a.Add(market.ContractRef{Name: contract.Name, UID: string(contract.UID)}, refs, skip == nil || refs.Accept == "")
	}
	return a
}

func (q *Requester) contractOf(ref market.ContractRef) (v1alpha1.ComputeContract, bool) {
	contract, ok := q.contracts.Get(ref.Name)
	if !ok || (ref.UID != "" && string(contract.UID) != ref.UID) {
		return v1alpha1.ComputeContract{}, false
	}
	return contract, true
}

func (q *Requester) chaseReceipt(ctx context.Context, a *market.Addressees, record map[string]any) (market.ContractRef, bool) {
	event, err := market.ParseEvent(record)
	if err != nil {
		return market.ContractRef{}, false
	}
	receipt, err := q.repo.Resolve(ctx, market.RecordRef{URI: event.Receipt.URI, CID: event.Receipt.CID})
	if err != nil {
		return market.ContractRef{}, false
	}
	return a.ReceiptMatch(receipt.Value)
}

func (q *Requester) recordOwner(ctx context.Context, collection, author string, record map[string]any) (v1alpha1.ComputeContract, bool) {
	a := q.addressees(nil)
	ref, chase, ok := a.RequesterMatch(collection, author, record)
	if !ok && chase != "" {
		ref, ok = q.chaseReceipt(ctx, a, record)
	}
	if !ok {
		return v1alpha1.ComputeContract{}, false
	}
	return q.contractOf(ref)
}

func contractOwners(contract v1alpha1.ComputeContract) []metav1.OwnerReference {
	return []metav1.OwnerReference{contractOwnerReference(contract)}
}

func (q *Requester) ownerOfSync(item v1alpha1.StateSync) (v1alpha1.ComputeContract, bool) {
	for _, ref := range item.OwnerReferences {
		if ref.Kind != "ComputeContract" {
			continue
		}
		if contract, ok := q.contractOf(market.ContractRef{Name: ref.Name, UID: string(ref.UID)}); ok {
			return contract, true
		}
	}
	return v1alpha1.ComputeContract{}, false
}

func (q *Requester) inboxVerdict(item v1alpha1.StateSync) inboxVerdict {
	switch item.Spec.Sink.Adapter {
	case MarketInboxAdapter, BidInboxAdapter:
		payload, err := decodeMarketInboxPayload(item.Spec.Payload)
		if err != nil || !market.RequesterConsumes(payload.Collection) {
			return inboxForeign
		}
		if _, _, ok := q.addressees(market.RequesterContractEnded).RequesterMatch(payload.Collection, payload.DID, payload.Record); ok {
			return inboxKeep
		}
		_, chase, ok := q.addressees(nil).RequesterMatch(payload.Collection, payload.DID, payload.Record)
		switch {
		case ok:
			return inboxEnded
		case chase != "":
			if contract, owned := q.ownerOfSync(item); owned {
				if market.RequesterContractEnded(contract) {
					return inboxEnded
				}
				return inboxKeep
			}
			return inboxUnknown
		}
		return inboxForeign
	case InboxAdapter:
		var payload inboxEventPayload
		if err := json.Unmarshal(item.Spec.Payload, &payload); err != nil || payload.Event.URI == "" {
			return inboxForeign
		}
		contract, ok := q.contractForReceiptURI(payload.Event.ReceiptURI)
		if !ok {
			contract, ok = q.ownerOfSync(item)
		}
		switch {
		case !ok:
			return inboxUnknown
		case contractPhaseDone(contract.Status.Phase):
			return inboxEnded
		}
		return inboxKeep
	}
	return inboxKeep
}

func (q *Requester) collectInbox(ctx context.Context, item v1alpha1.StateSync) (time.Duration, bool) {
	if !q.synced.Load() || item.DeletionTimestamp != nil {
		return 0, false
	}
	verdict := q.inboxVerdict(item)
	switch verdict {
	case inboxKeep:
		return 0, false
	case inboxEnded:
		if item.Spec.Sink.Adapter == MarketInboxAdapter && item.Status.Phase != v1alpha1.StateSyncPhaseAcked {
			return 0, false
		}
		fallthrough
	case inboxUnknown:
		if left := item.CreationTimestamp.Add(MarketInboxRetention).Sub(q.now()); left > 0 {
			return left, false
		}
	}
	q.deleteMailbox(ctx, item.Name, "requester_inbox_prune_failed")
	if _, ok := q.syncs.Get(item.Name); ok {
		return 0, false
	}
	q.inboxed.Delete(string(item.UID) + "#" + item.Spec.IdempotencyKey)
	var n int64
	if verdict == inboxEnded {
		n = q.prunedEnded.Add(1)
	} else {
		n = q.prunedForeign.Add(1)
	}
	if n == 1 || n%inboxPrunedLogEvery == 0 {
		q.log.Info("requester_inbox_pruned", "foreign", q.prunedForeign.Load(), "ended", q.prunedEnded.Load(), "adapter", item.Spec.Sink.Adapter)
	}
	return 0, true
}