@@ -72,12 +72,47 @@ func (s *Sharing) SendInvitations(inst *instance.Instance, perms *permission.Per
7272 })
7373 }
7474 errg := g .Wait ()
75- if err := couchdb . UpdateDoc (inst , s ); err != nil {
75+ if err := s . updateMemberStatusesWithRetry (inst ); err != nil {
7676 return err
7777 }
7878 return errg
7979}
8080
81+ // updateMemberStatusesWithRetry persists the current member statuses to
82+ // CouchDB, retrying on conflict. A conflict can happen when ProcessAnswer
83+ // updates the sharing concurrently (e.g. during auto-accept).
84+ // On retry, the document is reloaded and statuses are re-applied, but
85+ // members already in Ready state are never downgraded.
86+ func (s * Sharing ) updateMemberStatusesWithRetry (inst * instance.Instance ) error {
87+ desiredStatuses := make (map [int ]string , len (s .Members ))
88+ for i , m := range s .Members {
89+ if i > 0 {
90+ desiredStatuses [i ] = m .Status
91+ }
92+ }
93+
94+ maxRetries := 3
95+ for attempt := 0 ; ; attempt ++ {
96+ err := couchdb .UpdateDoc (inst , s )
97+ if err == nil || ! couchdb .IsConflictError (err ) || attempt >= maxRetries {
98+ return err
99+ }
100+ s , err = FindSharing (inst , s .SID )
101+ if err != nil {
102+ return err
103+ }
104+ for i , status := range desiredStatuses {
105+ if i >= len (s .Members ) {
106+ continue
107+ }
108+ if s .Members [i ].Status == MemberStatusReady {
109+ continue
110+ }
111+ s .Members [i ].Status = status
112+ }
113+ }
114+ }
115+
81116// SendInvitationsToMembers sends mails from a recipient (open_sharing) to
82117// their contacts to invite them
83118func (s * Sharing ) SendInvitationsToMembers (inst * instance.Instance , members []Member , states map [string ]string ) error {
0 commit comments