Commit 4ff6b40

Nick Faro committed on
Lock before advertising push refs
commit 4ff6b403852573033470d55a746256c3fddb5f1e parent 989ca7f
2 changed files +65−8
Modifiedinternal/helper/helper.go +12−1
@@ -34,6 +34,11 @@ func Run(ctx context.Context, rawURL string, stdin io.Reader, stdout, stderr io.
34 publisher.AfterPublish = func(ctx context.Context, result publish.Result) { 34 publisher.AfterPublish = func(ctx context.Context, result publish.Result) {
35 publishSite(ctx, publisher, result, stderr) 35 publishSite(ctx, publisher, result, stderr)
36 } 36 }
37 defer func() {
38 if err := publisher.Abort(context.WithoutCancel(ctx)); err != nil {
39 fmt.Fprintf(stderr, "dumbforge: warning: release unused push lock: %v\n", err)
40 }
41 }()
37 42
38 scanner := bufio.NewScanner(stdin) 43 scanner := bufio.NewScanner(stdin)
39 writer := bufio.NewWriter(stdout) 44 writer := bufio.NewWriter(stdout)
@@ -49,7 +54,13 @@ func Run(ctx context.Context, rawURL string, stdin io.Reader, stdout, stderr io.
49 return err 54 return err
50 } 55 }
51 case line == "list" || line == "list for-push": 56 case line == "list" || line == "list for-push":
52 metadata, err := publisher.List(ctx) 57 var metadata publish.Metadata
58 var err error
59 if line == "list for-push" {
60 metadata, err = publisher.Prepare(ctx)
61 } else {
62 metadata, err = publisher.List(ctx)
63 }
53 if err != nil { 64 if err != nil {
54 return err 65 return err
55 } 66 }
Modifiedinternal/publish/publish.go +53−7
@@ -43,12 +43,49 @@ type Publisher struct {
43 Git gitutil.Git 43 Git gitutil.Git
44 Progress func(format string, args ...any) 44 Progress func(format string, args ...any)
45 AfterPublish func(context.Context, Result) 45 AfterPublish func(context.Context, Result)
46 prepared *preparedPush
47 }
48
49 type preparedPush struct {
50 lock *s3store.Lock
51 metadata Metadata
46 } 52 }
47 53
48 func (p *Publisher) List(ctx context.Context) (Metadata, error) { 54 func (p *Publisher) List(ctx context.Context) (Metadata, error) {
49 return LoadMetadata(ctx, p.Store) 55 return LoadMetadata(ctx, p.Store)
50 } 56 }
51 57
58 // Prepare serializes a push before refs are advertised. Holding the lock from
59 // advertisement through publication removes a redundant metadata read without
60 // weakening the concurrent-update check.
61 func (p *Publisher) Prepare(ctx context.Context) (Metadata, error) {
62 if p.prepared != nil {
63 return p.prepared.metadata, nil
64 }
65 lock, err := p.Store.AcquireLock(ctx)
66 if err != nil {
67 return Metadata{}, err
68 }
69 metadata, err := LoadMetadata(ctx, p.Store)
70 if err != nil {
71 _ = lock.Release(context.WithoutCancel(ctx))
72 return Metadata{}, err
73 }
74 p.prepared = &preparedPush{lock: lock, metadata: metadata}
75 return metadata, nil
76 }
77
78 // Abort releases a prepared push if the remote-helper session ends before it
79 // sends a push command.
80 func (p *Publisher) Abort(ctx context.Context) error {
81 if p.prepared == nil {
82 return nil
83 }
84 lock := p.prepared.lock
85 p.prepared = nil
86 return lock.Release(ctx)
87 }
88
52 // Push applies a batch as one publication. expected contains the refs advertised 89 // Push applies a batch as one publication. expected contains the refs advertised
53 // to Git before it sent the push commands and prevents a concurrent update from 90 // to Git before it sent the push commands and prevents a concurrent update from
54 // being silently overwritten. 91 // being silently overwritten.
@@ -63,9 +100,22 @@ func (p *Publisher) Push(ctx context.Context, updates []Update, expected map[str
63 if shallow { 100 if shallow {
64 return Result{}, fmt.Errorf("cannot publish from a shallow repository; fetch the complete history first") 101 return Result{}, fmt.Errorf("cannot publish from a shallow repository; fetch the complete history first")
65 } 102 }
66 lock, err := p.Store.AcquireLock(ctx) 103 var lock *s3store.Lock
67 if err != nil { 104 var metadata Metadata
68 return Result{}, err 105 if p.prepared != nil {
106 lock = p.prepared.lock
107 metadata = p.prepared.metadata
108 p.prepared = nil
109 } else {
110 lock, err = p.Store.AcquireLock(ctx)
111 if err != nil {
112 return Result{}, err
113 }
114 metadata, err = LoadMetadata(ctx, p.Store)
115 if err != nil {
116 _ = lock.Release(context.WithoutCancel(ctx))
117 return Result{}, err
118 }
69 } 119 }
70 defer func() { 120 defer func() {
71 if releaseErr := lock.Release(context.WithoutCancel(ctx)); err == nil && releaseErr != nil { 121 if releaseErr := lock.Release(context.WithoutCancel(ctx)); err == nil && releaseErr != nil {
@@ -73,10 +123,6 @@ func (p *Publisher) Push(ctx context.Context, updates []Update, expected map[str
73 } 123 }
74 }() 124 }()
75 125
76 metadata, err := LoadMetadata(ctx, p.Store)
77 if err != nil {
78 return Result{}, err
79 }
80 if err := checkExpected(metadata, updates, expected); err != nil { 126 if err := checkExpected(metadata, updates, expected); err != nil {
81 return Result{}, err 127 return Result{}, err
82 } 128 }