Redfs ubuntu resolute 7.0.0 14.14 - #206
Open
hbirth wants to merge 37 commits into
Open
Conversation
This is especially needed for better ftrace analysis, for example to build histograms. So far the request unique was missing, because it was added after the first trace message. IDs/req-unique now might not come up perfectly sequentially anymore, but especially with cloned device or io-uring this did not work perfectly anyway. Signed-off-by: Bernd Schubert <bschubert@ddn.com> (imported from commit 4415892) (cherry picked from commit 02f0a37) [hbi: 7.0 assigns the unique in exported send helpers that also trace; keep them, assigning only when fuse_args_to_req() has not]
Sometimes the file offset alignment needs to be opt-in to achieve the optimum performance at the backend store. For example when ErasureCode [1] is used at the backend store, the optimum write performance is achieved when the WRITE request is aligned with the stripe size of ErasureCode. Otherwise a non-aligned WRITE request needs to be split at the stripe size boundary. It is quite costly to handle these split partial requests, as firstly the whole stripe to which the split partial request belongs needs to be read out, then overwrite the read stripe buffer with the request, and finally write the whole stripe back to the persistent storage. Thus the backend store can suffer severe performance degradation when WRITE requests can not fit into one stripe exactly. The write performance can be 10x slower when the request is 256KB in size given 4MB stripe size. Also there can be 50% performance degradation in theory if the request is not stripe boundary aligned. Besides, the conveyed test indicates that, the non-alignment issue becomes more severe when decreasing fuse's max_ratio, maybe partly because the background writeback now is more likely to run parallelly with the dirtier. fuse's max_ratio ratio of aligned WRITE requests ---------------- ------------------------------- 70 99.9% 40 74% 20 45% 10 20% With the patched version, which makes the alignment constraint opt-in when constructing WRITE requests, the ratio of aligned WRITE requests increases to 98% (previously 20%) when fuse's max_ratio is 10. fuse: fix alignment to work with redfs ubuntu - small fix to make the fuse alignment patch work with redfs ubuntu 6.8.x - add writeback_control to fuse_writepage_need_send() to make more accurate decisions about when to skip sending data - fix shift number for FUSE_ALIGN_PG_ORDER - remove test code [1] https://lore.kernel.org/linux-fsdevel/20240124070512.52207-1-jefflexu@linux.alibaba.com/T/#m9bce469998ea6e4f911555c6f7be1e077ce3d8b4 Signed-off-by: Jingbo Xu <jefflexu@linux.alibaba.com> Signed-off-by: Bernd Schubert <bschubert@ddn.com> Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (imported from commit 5e590a6) (cherry picked from commit 1ee8c80) [hbi: 7.0 shares fuse_folios_need_send() with the read path, so the alignment check goes in fuse_iomap_writeback_range()]
Simplify fuse_compound_req to hold only the pointers to the added fuse args and the request housekeeping. Simplify open+getattr call by using helper functions to fill out the fuse request parameters Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (imported from commit 1607a03) (imported from commit 9df5e4c) (imported from commit 9921bcd) (imported from commit 09d6f59) (cherry picked from commit 41b40bd)
In preparation for allowing partial attribute updates via statx (where only a subset of STATX_BASIC_STATS may be requested and returned), modify fuse_change_attributes_common() to be more selective about what it updates. Currently, fuse_change_attributes_common() unconditionally: 1. Clears ALL of STATX_BASIC_STATS from inval_mask 2. Updates fi->i_time (extending the attribute timeout) For some fuse-servers it might be benefitial to reduce the number of queries attributes and and attribute mask is one of the features of statx. With the all or nothing handling of fuse_change_attributes_common() that statx feature is impossible to be used. This commit adds the logic to: 1. Track which attributes were actually returned (via sx->mask for statx, or assume all STATX_BASIC_STATS for getattr) 2. Only clear those specific attributes from inval_mask 3. Only update fi->i_time when it's safe: when cache_mask is empty OR when all cache_mask attributes were included in the response The condition in fuse_do_statx() still requires ALL STATX_BASIC_STATS, so this commit has no functional change. A follow up commit will relax that condition to enable partial updates. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> (cherry picked from commit 8034f26) (cherry picked from commit bf71659)
…attributes For permission checks via inode_permission(), we only need mode, uid, and gid attributes. Previously, we requested all STATX_BASIC_STATS, which was inefficient. This commit enables the optimization by: 1. Requesting only STATX_MODE | STATX_UID | STATX_GID for permission checks 2. Relaxing the condition in fuse_do_statx() from requiring all basic stats to accepting any subset of basic stats 3. Adding validation that the server returns at least what was requested The preparation commit ensures partial updates work correctly by only updating returned attributes and managing timeouts appropriately. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> (cherry picked from commit 09ed47b) (cherry picked from commit 7a08fe8)
fuse_perm_getattr() requests only STATX_MODE | STATX_UID | STATX_GID. fi->i_time is only advanced when the response covers STATX_BASIC_STATS in full, so a perm-only refresh clears the perm bits from inval_mask but leaves i_time at its old (expired) value. Add fi->i_perm_time, updated whenever a refresh covers MODE|UID|GID (always for the non-statx getattr path; conditionally on returned_attrs for the statx path), and gate fuse_permission()'s sync decision on it instead of i_time. fuse_update_get_attr() continues to use i_time so a full stat() still re-queries the server when size/mtime/etc. expire. Fixes: 09ed47b ("fuse: Optimize statx for permission checks by requesting only needed attributes") Signed-off-by: Horst Birthelmer <horst@birthelmer.de> (cherry picked from commit a2d3971) (cherry picked from commit fa98cf9)
Don't assume that the name is null terminated in fuse_do_lookupx() That holds when name is taken from a dentry's d_name (dcache invariant), but the 6.14 d_revalidate signature change (commit 5be1fa8) makes the callback receive the walker's qstr, whose backing buffer is the pathname being resolved. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit e3501c1)
On the pinned-header send path fuse_uring_dispatch_ent() calls io_uring_cmd_done() directly from the request submitter's context with IO_URING_F_UNLOCKED. Every fuse-uring command is marked cancelable, so io_uring_cmd_del_cancelable() has to take ctx->uring_lock from that foreign task on every request. The ring task holds this mutex for the whole ->uring_cmd() issue path (io_uring_enter() submission), where it also wakes the submitter of the request it just committed - before releasing the lock. The freshly woken submitter usually preempts the ring task on the same CPU, and its next dispatch then blocks on the very mutex its victim still holds. The preempted owner is merely runnable and gets no wakeup boost, so under CPU pressure this convoy costs milliseconds per request while the daemon's actual work is a few microseconds. Keep the copies into the pinned pages in the submitter's context - that is the point of the pinning - but defer the command completion to ring task task-work, like the non-pinned path already does. There io_uring_cmd_del_cancelable() runs under the task-work batch's already held uring_lock, and the submitter no longer touches ctx->uring_lock at all on the fast path (only the rare copy-failure fallback still can). Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit 6470c72) [hbi: 7.0 task work takes io_req_tw_func_t; use tw.cancel in place of IO_URING_F_TASK_DEAD]
fuse_uring_commit_fetch() runs from the ->uring_cmd() issue path with ctx->uring_lock held by io_uring_enter(). Ending the committed request there means wake_up(&req->waitq) runs while the ring task still holds the mutex, and the freshly woken submitter typically preempts the ring task on the same CPU right away - leaving the lock held by a merely runnable task for however long the runqueue is backed up. wake_up_sync() would not help here: WF_SYNC only biases task placement in select_task_rq_fair(), check_preempt_wakeup_fair() never looks at it, so it cannot stop the wakee from preempting the waker. Instead defer fuse_request_end() to task work on the ring task. It runs after the submission path has dropped uring_lock - at latest on return to userspace, typically at io_cqring_wait() entry via io_run_task_work() - so the ring task finishes its critical section and the entry recycling in fuse_uring_next_fuse_req() before the submitter gets the CPU. This also moves the request ->end() callbacks off the locked section, shortening the uring_lock hold time. Fall back to completing in place when the command was issued from an unlocked context (io-wq punt) or task_work_add() fails because the task is exiting. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit b6ec467)
The append grant is taken at a sampled i_size, but the exclusive inode lock does not pin it: attribute replies move i_size under fi->lock alone. generic_write_checks() then rewrites ki_pos to the fresher i_size and the write lands past the granted range, dirtying the cache without DLM coverage. The in-gate re-validation checks the stale range and cannot catch it. Request the lock again for the final ki_pos when it moved, and let dlm_pos track it so the in-gate re-validation guards the range the write really lands in. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit c93c346)
Read-modify-writes of blocks past the server EOF go back to reading: the zero-fill needed fi->server_size verified against every recorded write grant, and the verification GETATTRs are server traffic and complexity the saved READs are not worth. Partial pages of expanding writes will be sent as separate FUSE_WRITE requests instead, which needs no read-modify-write at all. Remove the fi->server_size bound with its grow and shrink sites, the grant verification debt, and the zero-fill in fuse_iomap_read_folio_range(). Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit ea43157)
With the expansion zero-fill gone, every unaligned buffered write under DLM pays a read-modify-write READ per partial page, and past the server EOF that READ returns zero bytes: a wasted round trip for data that cannot exist. The write path cannot tell the wasted READ from a needed one without trusting a bound the grant may predate. Do not complete partial pages at all. Split the buffered write (fuse_dlm_buffered_write()): cache only the page-aligned interior, whole pages need no read-modify-write, and send the unaligned head and tail through the existing writethrough path (fuse_perform_write()). A partial page is written byte-exact and left non-uptodate, so nothing is read and neighbouring writers sharing a boundary page accumulate their bytes on the server. A sub-page write with no aligned interior goes fully through. The DLM write lock is taken over the whole range before i_rwsem, so all chunks are covered, and the pre-claimed i_size extension with its reconciliation keeps working on the chunk total. The read-modify-write path stays functional for the rare unaligned restart of the interior after a faulted user copy. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit ae035fb)
Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit 41b92e4)
fuse_dlm_buffered_write() sends the unaligned edges of a write to the server itself so no partly written block is dirtied, and cuts at PAGE_SIZE. Nothing checked that a block is a page: a fuseblk mount keeps whatever blksize= asked for. iomap then goes back for the remainder of an edge block and dirties what the write already sent. Refuse the connection at FUSE_INIT when a writeback cache is negotiated on a block that is not a page. st_blksize still reports what the server named. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit 7499e44) [hbi: 7.0 dropped fc->blkbits and never sets inode->i_blkbits from the server, so check sb->s_blocksize_bits and drop the per-inode pin]
Readahead fills the page cache past the range fuse_cache_read_iter() locked, so those folios get no revoke when a remote node writes them. Request a read grant over the whole window in fuse_readahead() before any folio is consumed, and skip the window when the request fails. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com> (cherry picked from commit 8c2ff0e) [hbi: 7.0 reads ahead through iomap_readahead(); take the grant before it]
The record was an interval tree of ranges carrying a lock mode and a revoke generation, walked under one lock per inode. Splitting and merging ranges on every grant made it the contended structure on a file several threads write. Record coverage as two bitmaps per aligned file region instead, a bit per page for granted and for granted-for-write. A region is a fixed span, so recording a grant neither allocates nor rearranges anything, and the regions live in an xarray, so threads writing far apart never touch the same one. A grant still on the wire stays out of the bitmaps and waits on a pending list where a revoke marks it, which keeps a reply that crosses a revoke from recording coverage the server took back. Adapt the paths to it: reads and readahead take a read grant, a cached write takes the write grant for the range it dirties and sends its unaligned edges straight through, writeback confirms the grant for every run it sends, and a NOTIFY invalidate drops the record for the range it revokes before it drops the page cache. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse_writeback_write_iter() published its AOP_TRUNCATED_PAGE retry state in a connection-wide xarray keyed by task pointer, and erased it again, once per buffered write. A task pointer is a high index, so every write built and tore down the whole node chain down to it under one lock shared by every writer of the mount, and its GFP_KERNEL allocation ran with i_rwsem held. iomap already carries a private pointer through to fuse_iomap_read_folio_range(), where fuse put the struct file. Put the file and the flag in one struct on the stack and pass that instead. The store could fail and take the write down with it, and the load could come back empty and drop a needed retry. Neither is reachable now. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
A server answers a contended range with -EDEADLK, which fuse_do_readfolio() turns into AOP_TRUNCATED_PAGE so the read is retried. __fuse_get_dlm_lock() returned it instead, and its writeback caller has no way to hold on to a folio it reports an error for: iomap takes the dirty flag off before offering the folio and clears its dirty ranges whatever the callback returns, so the bytes are dropped without ever reaching the server. A NOTIFY invalidate then drops the now clean folio and the next read gets the pre-write content. Ask again on -EDEADLK and -EAGAIN, on the retry budget the killed-in- flight grant already uses. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
iomap clears the dirty flag before it offers a folio to ->writeback_range and clears its dirty ranges whatever the callback returns, and nothing puts either back. A run that reports an error has therefore dropped its bytes: the folio stays in the page cache, clean and uptodate, holding data the server never received, until an invalidate drops it and the next read returns what the server has. The comment claimed the opposite. Redirty the folio instead, for the grant and the allocation, while there is still a connection to send it on. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
The DLM buffered write holds i_rwsem shared and claims its i_size extension before it dirties anything. fuse_attr_cache_mask() decides whether the server's size wins from an i_size it reads before that claim, and sleeps in the lock tree query before fuse_change_attributes_i() applies the answer under fi->lock. A GETATTR that left while i_size still matched the server's is therefore applied over every claim made since, and i_size drops by exactly the writes in flight. truncate_pagecache() then zeroes the tail of the page holding the new size and drops what is above, which writeback sends as zeros. Move attr_version on the claim so those replies are dropped, the same reason fuse_write_update_attr() moves it, and count the writers whose claim is not yet dirty for a reply that leaves after one. FUSE_I_SIZE_UNSTABLE cannot serve as the count: it is a single bit and every writer clears it. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse_reverse_inval_inode() drives this inode's page cache, before the revoke to flush it and after the revoke to drop it, on the thread that wrote the NOTIFY into /dev/fuse. Both reach fuse_iomap_writeback_range(), which holds the folio locked and under writeback while fuse_dlm_regrant_range() sends FUSE_DLM_WB_LOCK and waits. The range is the one being revoked, so the server cannot answer until the revoke completes, and the revoke cannot complete until the handler returns. A folio dirtied under a grant a NOTIFY took away is reached this way, which fuse_cache_write_iter() says is allowed to happen. Mark the task across that work and report the run as a hole when the grant is not already held: the folio goes back on the dirty list and an ordinary writeback sends it with a grant of its own. Reporting a hole means the run needs the type reset that had no user before. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse_iomap_writeback_range() runs with the folio locked and, since iomap_writeback_folio() starts the writeback before the run loop, under writeback as well. Asking the server for a grant there is the ordering Documentation/filesystems/fuse/fuse-AOP_TRUNCATED_PAGE-reason.txt exists to forbid: no cluster lock may be taken while a page lock is held. The read path has AOP_TRUNCATED_PAGE to unlock and retry with, and ->writeback_range has nothing of the sort, so the violation sits where the remedy does not reach. Skip a run whose grant has gone: report it as a hole, put the folio back on the dirty list and remember the range. fuse_iomap_writeback_submit() takes it back once the pass has let go of every folio, and the pass that follows sends it. Not from fuse_launder_folio(), which arrives with the folio locked by folio_unmap_invalidate(), nor from a revoke handler, which would ask for the range it is revoking. There the skip stands. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse_dlm_request_begin() publishes the range asked for, and fuse_dlm_kill_pending() can only test that one, but the commit records what the server granted, which may reach FUSE_DLM_MAX_EXTRA_GRANT either side of it. A revoke processed while the request was on the wire and landing in that excess marks nothing: the request does not overlap it, and the shards hold no bit for it yet because the grant is not recorded. The excess is then recorded over the revoked range and no later NOTIFY takes it back, which is the case the pending list exists to catch. Publish the widest bounds the commit could record as well, and separate the two outcomes. A revoke over the range asked for still kills the grant. One over the excess alone leaves the range asked for recorded and drops the excess, which only costs a re-request. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
The comment claimed the round trip happens before any folio of the window is locked. ->readahead is entered with all of them locked and readahead_folio() is what unlocks them, so it happens under them. Say so, and say why it does not close a cycle: the lock in the way belongs to the node the revoke is sent to, not to this one. Name the case that does not follow, a window this already holds part of. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse_iomap_writeback_range() unions every run it had to skip, and fuse_iomap_writeback_submit() asks for the lot in one grant. A pass sweeping a large file skips runs gigabytes apart, so the union grows to the whole sweep and the request covers a range nothing wanted. Stop extending at one shard. The runs left out stay dirty and a later pass asks for them. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse_writeback_redirty() dirtied the folio from inside ->writeback_range, and iomap_writeback_folio() runs iomap_clear_range_dirty() over the whole folio once that returns. For a folio one block wide there is no iomap_folio_state and the call does nothing, so it worked. A large folio has one, and the folio is left with the dirty flag and no dirty block under it: the next pass finds nothing to write and the folio goes clean with its bytes never sent. Hold the folio instead and dirty it once iomap has released it, on the next call or in the submit. fuse_writepage_need_send() also reached wbc->range_end with no wbc, which only a folio of a single run kept out of reach; guard it. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse_fill_write_pages() carried the offset of the next copy across iterations as a within page residue and turned it into a folio offset by adding the page delta. A write that starts inside a folio it does not begin covers the rest of that folio, and the residue left from it is then added to the next folio's own delta: the copy lands past the end of the folio, copy_folio_from_iter_atomic() warns and returns nothing, and the fault in loop retries it for good. A folio one page wide cannot start before the write does, which is what kept it out of reach. Take the offset from @pos, which is always current, and read the loop's carry on test off the folio it just filled. fuse_page_mkwrite() locked the page that faulted. The whole folio is dirtied on the way out, in fault_dirty_shared_page(), so the DLM lock has to cover the folio. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
Two ways a cached write reached the write check as a hole. fuse_iomap_writeback_range() returns -EIO when the inode has no file open for writing, and dropped the folio doing so. fuse_open() invalidates the whole mapping unless the server sets FOPEN_KEEP_CACHE, and reaches fuse_launder_folio() that way; by the time a reader opens the file the last writer has closed, so fuse_write_file_get() finds nothing, warns, and the bytes go. They are still the newest there are, so keep them. And a run whose grant had gone is skipped, its range taken back in the submit, and the folio left dirty for a later pass. A data integrity writeback has no later pass, so fsync() and close() reported bytes written that were still only in the page cache. Go round again while anything is left deferred. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
A writer confirms its grant and then copies and dirties, and a revoke landing in between is answered while those bytes are in no page cache and on no wire. The flush the revoke runs cannot find them, so they go out later under a fresh grant, after the server has handed the lock to another node. Add a pin: the range a writer is about to dirty, on a list a revoke walks before it removes anything. A revoke publishes the range it takes away on a second list and waits for the pins overlapping it, and a pin overlapping a published range is refused. Refusal and wait test the same overlap, so the wait converges, and ranges that do not overlap never meet: a notify leaves the rest of the file writable and a write outside the notified range does not hold it up. Both nodes are caller storage, so nothing is allocated to take a pin and the writeback path can take one with a folio held. A pin is found again by owner, since iomap hands ->put_folio the inode and nothing of the iteration. Confirm the grant under the pin, never before, in the three places bytes become the server's: the iomap interior through new ->get_folio and ->put_folio hooks, the writethrough edges around their FUSE_WRITE, and the writeback run until the folio is under writeback. A grant that has gone is asked for again with the pin dropped, since that request is answered by the server the revoke came from. With the writers fenced, the flush a revoke runs needs a single pass: nothing can turn up dirty behind it. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
A folio wider than a block carries per-block dirty state, and iomap_writeback_folio() clears all of it after ->writeback_range. A run fuse defers and puts back with folio_mark_dirty() comes back dirty over the whole folio, since iomap_dirty_folio() sets the whole range, so the next pass sends blocks this client never wrote: data read from the server, or zeros laid down above the size the file had at the time. Order-0 folios carry no per-block state, so a dirty folio is one this client wrote whole and the widening cannot happen. The parameter stays writable for anyone opting back in. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
The !CONFIG_FUSE_IO_URING branch carries a second stub for it, declared bool and returning nothing, so fs/fuse does not build without io-uring. It came in with the fuse_uring_flush_bg() stub, which is the one that hunk was meant to add. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse: fine-grained request ftraces split the one tracepoint in two:
trace_fuse_request_enqueue() where a request is queued, and
trace_fuse_request_send() where it reaches the server, so the time in
each is separable. 7.0 moved its own trace_fuse_request_send() out of
fuse_send_one() into fuse_request_assign_unique{,_locked}(), which the
split did not reach, so every request is traced sent twice: once at queue
time and once for real.
Drop the send from the assign helpers. It stays in fuse_dev_do_read()
and fuse_uring_send(), and virtiofs is left with the enqueue trace its
send_req already stands for.
Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
fuse_uring_args_to_ring() finished the copy state twice: the 7.0 line after fuse_copy_args() and the copy_finish label that already covers both exits. fuse_uring_create() kept a kcalloc() while every allocation around it moved to kzalloc_objs(), and lost its continuation indent doing so. io_pages_free() and the queue map destructors were hoisted above their neighbours for no reason. No functional change; the remaining delta to the 6.17 branch is now the 7.0 API differences alone. Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
hbirth
force-pushed
the
redfs-ubuntu-resolute-7.0.0-14.14
branch
from
September 1, 2026 12:29
bde846e to
0b2ea3e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.