Submit a write and block until it's durable. Returns the assigned LSN once the batch containing this write has been fsynced to disk. If fsync fails, the error is propagated to all threads whose writes were in the failed batch.
(&self, writer: &Mutex<WalWriter>, write: PendingWrite)
| 104 | /// fsynced to disk. If fsync fails, the error is propagated to all threads |
| 105 | /// whose writes were in the failed batch. |
| 106 | pub fn submit(&self, writer: &Mutex<WalWriter>, write: PendingWrite) -> Result<CommitResult> { |
| 107 | // Enqueue the write. |
| 108 | { |
| 109 | let mut pending = self.pending.lock().map_err(|_| WalError::LockPoisoned { |
| 110 | context: "pending queue", |
| 111 | })?; |
| 112 | pending.push(write); |
| 113 | } |
| 114 | |
| 115 | // Try to become the commit leader. If another thread holds the commit |
| 116 | // lock, we block here until it finishes — then we check if our write |
| 117 | // was already committed by that leader. |
| 118 | let _commit_guard = self |
| 119 | .commit_lock |
| 120 | .lock() |
| 121 | .map_err(|_| WalError::LockPoisoned { |
| 122 | context: "commit lock", |
| 123 | })?; |
| 124 | |
| 125 | // Drain pending writes. If the previous leader already committed our |
| 126 | // write, the pending queue will be empty (our write was drained by |
| 127 | // that leader). If the queue is non-empty, we are the new leader. |
| 128 | let batch: Vec<PendingWrite> = { |
| 129 | let mut pending = self.pending.lock().map_err(|_| WalError::LockPoisoned { |
| 130 | context: "pending queue (drain)", |
| 131 | })?; |
| 132 | std::mem::take(&mut *pending) |
| 133 | }; |
| 134 | |
| 135 | if batch.is_empty() { |
| 136 | // Previous leader drained our write. Check if that commit |
| 137 | // succeeded or failed. This is the critical fix: without this |
| 138 | // check, a non-leader would return durable:true even if the |
| 139 | // leader's fsync failed. |
| 140 | if self.last_commit_failed.load(Ordering::Acquire) { |
| 141 | return Err(WalError::Io(std::io::Error::other( |
| 142 | "WAL fsync failed in previous group commit batch", |
| 143 | ))); |
| 144 | } |
| 145 | let lsn = self.durable_lsn.load(Ordering::Acquire); |
| 146 | return Ok(CommitResult { lsn, durable: true }); |
| 147 | } |
| 148 | |
| 149 | // We are the leader — append and fsync. |
| 150 | let mut wal = writer.lock().map_err(|_| WalError::LockPoisoned { |
| 151 | context: "WAL writer", |
| 152 | })?; |
| 153 | let mut last_lsn = 0; |
| 154 | |
| 155 | for w in &batch { |
| 156 | last_lsn = wal.append( |
| 157 | w.record_type, |
| 158 | w.tenant_id, |
| 159 | w.vshard_id, |
| 160 | w.database_id, |
| 161 | &w.payload, |
| 162 | )?; |
| 163 | } |