Merge changes I06a504a9,I54acfc52,I512ddcd0,Ic0288231
* changes:
scouting: Allow background tasks to cancel themselves
scouting: Change background_task implementation
scouting: Make background_task interval configurable
Make the scouting background scraper more generic
diff --git a/scouting/background_task/BUILD b/scouting/background_task/BUILD
new file mode 100644
index 0000000..bdf7190
--- /dev/null
+++ b/scouting/background_task/BUILD
@@ -0,0 +1,15 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "background_task",
+ srcs = ["background_task.go"],
+ importpath = "github.com/frc971/971-Robot-Code/scouting/background_task",
+ target_compatible_with = ["@platforms//cpu:x86_64"],
+ visibility = ["//visibility:public"],
+)
+
+go_test(
+ name = "background_task_test",
+ srcs = ["background_task_test.go"],
+ embed = [":background_task"],
+)
diff --git a/scouting/background_task/background_task.go b/scouting/background_task/background_task.go
new file mode 100644
index 0000000..3d7d29a
--- /dev/null
+++ b/scouting/background_task/background_task.go
@@ -0,0 +1,58 @@
+package background_task
+
+import (
+ "time"
+)
+
+// A helper to run a function in the background at a specified interval.
+// Can be used for a lot of different things.
+type backgroundTask struct {
+ ticker *time.Ticker
+ stopRequested chan bool
+ done chan bool
+}
+
+func New(interval time.Duration) backgroundTask {
+ return backgroundTask{
+ ticker: time.NewTicker(interval),
+ stopRequested: make(chan bool, 1),
+ done: make(chan bool, 1),
+ }
+}
+
+func (task *backgroundTask) Start(taskFunc func()) {
+ go func() {
+ // Signal the Stop() function below when the goroutine has
+ // finished executing.
+ defer func() { task.done <- true }()
+
+ // time.Ticker doesn't perform an immediate invocation.
+ // Instead, it waits for the specified duration before
+ // triggering the first tick. We pretend that there's a tick
+ // here by invoking the callback manually.
+ taskFunc()
+
+ for {
+ select {
+ case <-task.stopRequested:
+ return
+ case <-task.ticker.C:
+ taskFunc()
+ }
+ }
+ }()
+}
+
+// Stops the background task from within the background task. The Stop()
+// function still needs to be called from outside the task.
+func (task *backgroundTask) StopFromWithinTask() {
+ task.stopRequested <- true
+}
+
+func (task *backgroundTask) Stop() {
+ task.stopRequested <- true
+ task.ticker.Stop()
+ <-task.done
+ close(task.stopRequested)
+ close(task.done)
+}
diff --git a/scouting/background_task/background_task_test.go b/scouting/background_task/background_task_test.go
new file mode 100644
index 0000000..d4fb7ee
--- /dev/null
+++ b/scouting/background_task/background_task_test.go
@@ -0,0 +1,50 @@
+package background_task
+
+import (
+ "testing"
+ "time"
+)
+
+func TestBackgroundTask(t *testing.T) {
+ task := New(100 * time.Millisecond)
+ defer task.Stop()
+
+ counter := 0
+ task.Start(func() {
+ counter += 1
+ })
+
+ // Block until we've seeen 10 timer ticks.
+ for counter < 10 {
+ time.Sleep(100 * time.Millisecond)
+ }
+}
+
+func TestSelfCancellation(t *testing.T) {
+ task := New(100 * time.Millisecond)
+
+ done := false
+ counter := 0
+ task.Start(func() {
+ counter += 1
+
+ if done {
+ t.Fatal("callback should not be called after cancellation")
+ }
+
+ if counter == 10 {
+ task.StopFromWithinTask()
+ done = true
+ }
+ })
+
+ // Block until the background task has cancelled itself.
+ for !done {
+ time.Sleep(100 * time.Millisecond)
+ }
+
+ // Then sleep for a little longer to make sure that the task won't
+ // invoke the t.Fatal().
+ time.Sleep(time.Second)
+ task.Stop()
+}
diff --git a/scouting/scraping/background/BUILD b/scouting/scraping/background/BUILD
deleted file mode 100644
index 9aa92c9..0000000
--- a/scouting/scraping/background/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-go_library(
- name = "background",
- srcs = ["background.go"],
- importpath = "github.com/frc971/971-Robot-Code/scouting/scraping/background",
- target_compatible_with = ["@platforms//cpu:x86_64"],
- visibility = ["//visibility:public"],
-)
diff --git a/scouting/scraping/background/background.go b/scouting/scraping/background/background.go
deleted file mode 100644
index 5af8c3e..0000000
--- a/scouting/scraping/background/background.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package background
-
-import (
- "time"
-)
-
-// A helper to run a function in the background every ~10 minutes. Technically
-// can be used for a lot of different things, but is primarily geared towards
-// scraping thebluealliance.com.
-type BackgroundScraper struct {
- doneChan chan<- bool
- checkStopped chan<- bool
-}
-
-func (scraper *BackgroundScraper) Start(scrape func()) {
- scraper.doneChan = make(chan bool, 1)
- scraper.checkStopped = make(chan bool, 1)
-
- go func() {
- // Setting start time to 11 minutes prior so getRankings called instantly when Start() called
- startTime := time.Now().Add(-11 * time.Minute)
- for {
- curTime := time.Now()
- diff := curTime.Sub(startTime)
-
- if diff.Minutes() > 10 {
- scrape()
- startTime = curTime
- }
-
- if len(scraper.doneChan) != 0 {
- break
- }
-
- time.Sleep(time.Second)
- }
-
- scraper.checkStopped <- true
- }()
-}
-
-func (scraper *BackgroundScraper) Stop() {
- scraper.doneChan <- true
-
- for {
- if len(scraper.checkStopped) != 0 {
- close(scraper.doneChan)
- close(scraper.checkStopped)
- break
- }
- }
-}
diff --git a/scouting/webserver/BUILD b/scouting/webserver/BUILD
index 934b50a..f5b0a81 100644
--- a/scouting/webserver/BUILD
+++ b/scouting/webserver/BUILD
@@ -7,8 +7,8 @@
target_compatible_with = ["@platforms//cpu:x86_64"],
visibility = ["//visibility:private"],
deps = [
+ "//scouting/background_task",
"//scouting/db",
- "//scouting/scraping/background",
"//scouting/webserver/driver_ranking",
"//scouting/webserver/match_list",
"//scouting/webserver/rankings",
diff --git a/scouting/webserver/main.go b/scouting/webserver/main.go
index 1b5a002..c5bed2c 100644
--- a/scouting/webserver/main.go
+++ b/scouting/webserver/main.go
@@ -14,8 +14,8 @@
"syscall"
"time"
+ "github.com/frc971/971-Robot-Code/scouting/background_task"
"github.com/frc971/971-Robot-Code/scouting/db"
- "github.com/frc971/971-Robot-Code/scouting/scraping/background"
"github.com/frc971/971-Robot-Code/scouting/webserver/driver_ranking"
"github.com/frc971/971-Robot-Code/scouting/webserver/match_list"
"github.com/frc971/971-Robot-Code/scouting/webserver/rankings"
@@ -141,17 +141,17 @@
// Since Go doesn't support default arguments, we use 0 and "" to
// indicate that we want to source the values from the config.
- matchListScraper := background.BackgroundScraper{}
+ matchListScraper := background_task.New(10 * time.Minute)
matchListScraper.Start(func() {
match_list.GetMatchList(database, 0, "", *blueAllianceConfigPtr)
})
- rankingsScraper := background.BackgroundScraper{}
+ rankingsScraper := background_task.New(10 * time.Minute)
rankingsScraper.Start(func() {
rankings.GetRankings(database, 0, "", *blueAllianceConfigPtr)
})
- driverRankingParser := background.BackgroundScraper{}
+ driverRankingParser := background_task.New(10 * time.Minute)
driverRankingParser.Start(func() {
// Specify "" as the script path here so that the default is
// used.
diff --git a/scouting/webserver/rankings/BUILD b/scouting/webserver/rankings/BUILD
index 4696d26..5192e87 100644
--- a/scouting/webserver/rankings/BUILD
+++ b/scouting/webserver/rankings/BUILD
@@ -20,8 +20,8 @@
],
embed = [":rankings"],
deps = [
+ "//scouting/background_task",
"//scouting/db",
- "//scouting/scraping/background",
"//scouting/webserver/server",
],
)
diff --git a/scouting/webserver/rankings/rankings_test.go b/scouting/webserver/rankings/rankings_test.go
index 6f8af3b..4ab3c34 100644
--- a/scouting/webserver/rankings/rankings_test.go
+++ b/scouting/webserver/rankings/rankings_test.go
@@ -1,14 +1,15 @@
package rankings
import (
- "github.com/frc971/971-Robot-Code/scouting/db"
- "github.com/frc971/971-Robot-Code/scouting/scraping/background"
- "github.com/frc971/971-Robot-Code/scouting/webserver/server"
"net/http"
"reflect"
"strings"
"testing"
"time"
+
+ "github.com/frc971/971-Robot-Code/scouting/background_task"
+ "github.com/frc971/971-Robot-Code/scouting/db"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/server"
)
type MockDatabase struct {
@@ -37,7 +38,7 @@
func TestGetRankings(t *testing.T) {
database := MockDatabase{}
- scraper := background.BackgroundScraper{}
+ scraper := background_task.New(time.Minute)
tbaServer := server.NewScoutingServer()
tbaServer.Handle("/", ServeRankings(t, http.FileServer(http.Dir("../../"))))
tbaServer.Start(8000)