Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nOZ6b-0001eQ-UH for pgsql-hackers@arkaria.postgresql.org; Mon, 28 Feb 2022 06:01:58 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1nOZ6a-0004r6-Qa for pgsql-hackers@arkaria.postgresql.org; Mon, 28 Feb 2022 06:01:56 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nOZ6a-0004qx-F5 for pgsql-hackers@lists.postgresql.org; Mon, 28 Feb 2022 06:01:56 +0000 Received: from mail.postgrespro.ru ([93.174.131.139]) by makus.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1nOZ6X-0007q7-3K for pgsql-hackers@lists.postgresql.org; Mon, 28 Feb 2022 06:01:55 +0000 Received: from falcon-note (broadband-46-242-13-97.ip.moscow.rt.ru [46.242.13.97]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mail.postgrespro.ru (Postfix) with ESMTPSA id 152DA21C0389; Mon, 28 Feb 2022 09:01:50 +0300 (MSK) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=postgrespro.ru; s=mail; t=1646028110; bh=7yjQpMajuE1TOXSQUdoUUmDU+O7vsUpLHtkzcxpk/F0=; h=Subject:From:To:Cc:Date:In-Reply-To:References; b=hdmLs9tRUKiOE8pBLTR/yqD+DBfyf7wPF3TSGZkCXPqjweFXk92HHh0zFU1tRlHw5 rOkF+RiqmxYq9VHoKV+cy9rCWUfYw3JV3VIFggmjNIuECMQaSJ70E8ioIcYtKO/9Z0 4fAuEv2mI3HST+39PPCyT6LZh+hxhvoGkWyKbv7w= Message-ID: Subject: Re: BufferAlloc: don't take two simultaneous locks From: Yura Sokolov To: Andres Freund Cc: Kyotaro Horiguchi , michail.nikolaev@gmail.com, x4mmm@yandex-team.ru, pgsql-hackers@lists.postgresql.org Date: Mon, 28 Feb 2022 09:01:49 +0300 In-Reply-To: <20220225170127.lxe66l2swx3empgu@alap3.anarazel.de> References: <3b108afd19fa52ed20c464a69f64d545e4a14772.camel@postgrespro.ru> <20220217.141647.512059035403445205.horikyota.ntt@gmail.com> <1a97e80353d6855e9217cd6e2052257190a98f2d.camel@postgrespro.ru> <20220225080455.3zzxwfpvbizzcsfo@alap3.anarazel.de> <20220225170127.lxe66l2swx3empgu@alap3.anarazel.de> Organization: PostgresPro Content-Type: text/plain; charset="UTF-8" User-Agent: Evolution 3.36.5-0ubuntu1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk В Пт, 25/02/2022 в 09:01 -0800, Andres Freund пишет: > Hi, > > On 2022-02-25 12:51:22 +0300, Yura Sokolov wrote: > > > > + * The usage_count starts out at 1 so that the buffer can survive one > > > > + * clock-sweep pass. > > > > + * > > > > + * We use direct atomic OR instead of Lock+Unlock since no other backend > > > > + * could be interested in the buffer. But StrategyGetBuffer, > > > > + * Flush*Buffers, Drop*Buffers are scanning all buffers and locks them to > > > > + * compare tag, and UnlockBufHdr does raw write to state. So we have to > > > > + * spin if we found buffer locked. > > > > > > So basically the first half of of the paragraph is wrong, because no, we > > > can't? > > > > Logically, there are no backends that could be interesting in the buffer. > > Physically they do LockBufHdr/UnlockBufHdr just to check they are not interesting. > > Yea, but that's still being interested in the buffer... > > > > > > + * Note that we write tag unlocked. It is also safe since there is always > > > > + * check for BM_VALID when tag is compared. > > > > */ > > > > buf->tag = newTag; > > > > - buf_state &= ~(BM_VALID | BM_DIRTY | BM_JUST_DIRTIED | > > > > - BM_CHECKPOINT_NEEDED | BM_IO_ERROR | BM_PERMANENT | > > > > - BUF_USAGECOUNT_MASK); > > > > if (relpersistence == RELPERSISTENCE_PERMANENT || forkNum == INIT_FORKNUM) > > > > - buf_state |= BM_TAG_VALID | BM_PERMANENT | BUF_USAGECOUNT_ONE; > > > > + new_bits = BM_TAG_VALID | BM_PERMANENT | BUF_USAGECOUNT_ONE; > > > > else > > > > - buf_state |= BM_TAG_VALID | BUF_USAGECOUNT_ONE; > > > > - > > > > - UnlockBufHdr(buf, buf_state); > > > > + new_bits = BM_TAG_VALID | BUF_USAGECOUNT_ONE; > > > > > > > > - if (oldPartitionLock != NULL) > > > > + buf_state = pg_atomic_fetch_or_u32(&buf->state, new_bits); > > > > + while (unlikely(buf_state & BM_LOCKED)) > > > > > > I don't think it's safe to atomic in arbitrary bits. If somebody else has > > > locked the buffer header in this moment, it'll lead to completely bogus > > > results, because unlocking overwrites concurrently written contents (which > > > there shouldn't be any, but here there are)... > > > > That is why there is safety loop in the case buf->state were locked just > > after first optimistic atomic_fetch_or. 99.999% times this loop will not > > have a job. But in case other backend did lock buf->state, loop waits > > until it releases lock and retry atomic_fetch_or. > > > And or'ing contents in also doesn't make sense because we it doesn't work to > > > actually unset any contents? > > > > Sorry, I didn't understand sentence :(( > > You're OR'ing multiple bits into buf->state. LockBufHdr() only ORs in > BM_LOCKED. ORing BM_LOCKED is fine: > Either the buffer is not already locked, in which case it just sets the > BM_LOCKED bit, acquiring the lock. Or it doesn't change anything, because > BM_LOCKED already was set. > > But OR'ing in multiple bits is *not* fine, because it'll actually change the > contents of ->state while the buffer header is locked. First, both states are valid: before atomic_or and after. Second, there are no checks for buffer->state while buffer header is locked. All LockBufHdr users uses result of LockBufHdr. (I just checked that). > > > Why don't you just use LockBufHdr/UnlockBufHdr? > > > > This pair makes two atomic writes to memory. Two writes are heavier than > > one write in this version (if optimistic case succeed). > > UnlockBufHdr doesn't use a locked atomic op. It uses a write barrier and an > unlocked write. Write barrier is not free on any platform. Well, while I don't see problem with modifying buffer->state, there is problem with modifying buffer->tag: I missed Drop*Buffers doesn't check BM_TAG_VALID flag. Therefore either I had to add this check to those places, or return to LockBufHdr+UnlockBufHdr pair. For patch simplicity I'll return Lock+UnlockBufHdr pair. But it has measurable impact on low connection numbers on many-sockets. > > Greetings, > > Andres Freund