|
6 | 6 | "fmt" |
7 | 7 | "log/slog" |
8 | 8 | "os" |
| 9 | + "strconv" |
9 | 10 | "strings" |
10 | 11 | "sync" |
11 | 12 | "time" |
@@ -93,10 +94,12 @@ type Client struct { |
93 | 94 | retries int |
94 | 95 |
|
95 | 96 | // Cache for commit SHA to PR number lookups (for check event race condition) |
96 | | - commitPRCache map[string][]int // key: "owner/repo:sha", value: PR numbers |
97 | | - commitCacheKeys []string // track insertion order for LRU eviction |
98 | | - cacheMu sync.RWMutex |
99 | | - maxCacheSize int |
| 97 | + commitPRCache map[string][]int // key: "owner/repo:sha", value: PR numbers |
| 98 | + commitPRCacheExpiry map[string]time.Time // key: "owner/repo:sha", value: expiry time (only for empty results) |
| 99 | + commitCacheKeys []string // track insertion order for LRU eviction |
| 100 | + cacheMu sync.RWMutex |
| 101 | + maxCacheSize int |
| 102 | + emptyResultTTL time.Duration // TTL for empty results (to handle GitHub indexing race) |
100 | 103 | } |
101 | 104 |
|
102 | 105 | // New creates a new robust WebSocket client. |
@@ -127,13 +130,15 @@ func New(config Config) (*Client, error) { |
127 | 130 | } |
128 | 131 |
|
129 | 132 | return &Client{ |
130 | | - config: config, |
131 | | - stopCh: make(chan struct{}), |
132 | | - stoppedCh: make(chan struct{}), |
133 | | - logger: logger, |
134 | | - commitPRCache: make(map[string][]int), |
135 | | - commitCacheKeys: make([]string, 0, 512), |
136 | | - maxCacheSize: 512, |
| 133 | + config: config, |
| 134 | + stopCh: make(chan struct{}), |
| 135 | + stoppedCh: make(chan struct{}), |
| 136 | + logger: logger, |
| 137 | + commitPRCache: make(map[string][]int), |
| 138 | + commitPRCacheExpiry: make(map[string]time.Time), |
| 139 | + commitCacheKeys: make([]string, 0, 512), |
| 140 | + maxCacheSize: 512, |
| 141 | + emptyResultTTL: 30 * time.Second, // Retry empty results after 30s |
137 | 142 | }, nil |
138 | 143 | } |
139 | 144 |
|
@@ -640,6 +645,64 @@ func (c *Client) readEvents(ctx context.Context, ws *websocket.Conn) error { |
640 | 645 | eventNum := c.eventCount |
641 | 646 | c.mu.Unlock() |
642 | 647 |
|
| 648 | + // Populate cache from pull_request events to prevent cache misses |
| 649 | + // This ensures check events arriving shortly after PR creation can find the PR |
| 650 | + if event.Type == "pull_request" && event.CommitSHA != "" && strings.Contains(event.URL, "/pull/") { |
| 651 | + // Extract owner/repo/pr_number from URL |
| 652 | + parts := strings.Split(event.URL, "/") |
| 653 | + if len(parts) >= 7 && parts[2] == "github.com" && parts[5] == "pull" { |
| 654 | + owner := parts[3] |
| 655 | + repo := parts[4] |
| 656 | + prNum, err := strconv.Atoi(parts[6]) |
| 657 | + if err == nil && prNum > 0 { |
| 658 | + key := owner + "/" + repo + ":" + event.CommitSHA |
| 659 | + |
| 660 | + c.cacheMu.Lock() |
| 661 | + // Check if cache entry exists |
| 662 | + existing, exists := c.commitPRCache[key] |
| 663 | + if !exists { |
| 664 | + // New cache entry |
| 665 | + c.commitCacheKeys = append(c.commitCacheKeys, key) |
| 666 | + c.commitPRCache[key] = []int{prNum} |
| 667 | + c.logger.Debug("Populated cache from pull_request event", |
| 668 | + "commit_sha", event.CommitSHA, |
| 669 | + "owner", owner, |
| 670 | + "repo", repo, |
| 671 | + "pr_number", prNum) |
| 672 | + |
| 673 | + // Evict oldest 25% if cache is full |
| 674 | + if len(c.commitCacheKeys) > c.maxCacheSize { |
| 675 | + n := c.maxCacheSize / 4 |
| 676 | + for i := range n { |
| 677 | + delete(c.commitPRCache, c.commitCacheKeys[i]) |
| 678 | + } |
| 679 | + c.commitCacheKeys = c.commitCacheKeys[n:] |
| 680 | + } |
| 681 | + } else { |
| 682 | + // Check if PR number already in list |
| 683 | + found := false |
| 684 | + for _, existingPR := range existing { |
| 685 | + if existingPR == prNum { |
| 686 | + found = true |
| 687 | + break |
| 688 | + } |
| 689 | + } |
| 690 | + if !found { |
| 691 | + // Add PR to existing cache entry |
| 692 | + c.commitPRCache[key] = append(existing, prNum) |
| 693 | + c.logger.Debug("Added PR to existing cache entry", |
| 694 | + "commit_sha", event.CommitSHA, |
| 695 | + "owner", owner, |
| 696 | + "repo", repo, |
| 697 | + "pr_number", prNum, |
| 698 | + "total_prs", len(c.commitPRCache[key])) |
| 699 | + } |
| 700 | + } |
| 701 | + c.cacheMu.Unlock() |
| 702 | + } |
| 703 | + } |
| 704 | + } |
| 705 | + |
643 | 706 | // Handle check events with repo-only URLs (GitHub race condition) |
644 | 707 | // Automatically expand into per-PR events using GitHub API with caching |
645 | 708 | if (event.Type == "check_run" || event.Type == "check_suite") && event.CommitSHA != "" && !strings.Contains(event.URL, "/pull/") { |
|
0 commit comments