Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

res_ninit() requires cleanup with res_nclose() #12

Merged
merged 6 commits into from
Mar 28, 2023

Conversation

lyokha
Copy link
Collaborator

@lyokha lyokha commented Jun 23, 2021

Linux man 3 resolver says:
Every call to res_ninit() requires a corresponding call to
res_nclose() to free memory allocated by res_ninit() and subsequent
calls to res_nquery().

I use the library for periodic DNS polls from multiple async threads. Before the fix it was leaking.

@andreasabel
Copy link
Member

@Mergifyio rebase

@mergify
Copy link

mergify bot commented Aug 12, 2022

rebase

✅ Branch has been successfully rebased

@andreasabel
Copy link
Member

@lyokha : Could you suggest a reviewer please? (I do not have the necessary domain knowledge.)

@lyokha
Copy link
Collaborator Author

lyokha commented Aug 12, 2022

@lyokha : Could you suggest a reviewer please? (I do not have the necessary domain knowledge.)

I would recommend @ivanmurashko. He is an excellent engineer in this area.

@andreasabel
Copy link
Member

@ivanmurashko, would you help out and review these changes?

@ivanmurashko
Copy link

@ivanmurashko, would you help out and review these changes?

will do!

cbits/hs_resolv.h Show resolved Hide resolved
Copy link
Member

@andreasabel andreasabel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do with earlier and fewer placements of c_res_nclose?

fail "res_query(3) message size overflow"

errno <- getErrno

when (reslen < 0) $ do
unless (errno == eOK) $
unless (errno == eOK) $ do
c_res_nclose stptr
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this call to c_res_nclose be placed directly after getErrno, rather than 3 times in each branch of the case distinction over reslen and errno?
If it does not affect getErrno, it could even be placed directly after c_res_query, could it?
(Same in the other occurrences below.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are probably right, I'll test this.

@lyokha
Copy link
Collaborator Author

lyokha commented Aug 13, 2022

@andreasabel, regarding your comment about excessive use of c_res_nclose in multiple branches: you were right, this function can be called right after c_res_query and similar functions as soon it does not set errno according to man res_nclose (I also looked inside the glibc implementation). Currently, the patch against the upstream looks much simpler.

diff --git a/cbits/hs_resolv.h b/cbits/hs_resolv.h
index 37d486c..904b7d7 100644
--- a/cbits/hs_resolv.h
+++ b/cbits/hs_resolv.h
@@ -75,6 +75,14 @@ hs_res_query(struct __res_state *s, const char *dname, int class, int type, unsi
   return res_nquery(s, dname, class, type, answer, anslen);
 }
 
+inline static void
+hs_res_close(struct __res_state *s)
+{
+  assert(s);
+
+  res_nclose(s);
+}
+
 #else
 
 /* use non-reentrant API */
@@ -123,6 +131,11 @@ hs_res_query(void *s, const char *dname, int class, int type, unsigned char *ans
   return res_query(dname, class, type, answer, anslen);
 }
 
+inline static void
+hs_res_close(void *s)
+{
+}
+
 #endif
 
 #endif /* HS_RESOLV_H */
diff --git a/src/Network/DNS.hs b/src/Network/DNS.hs
index 5ae0828..1162155 100644
--- a/src/Network/DNS.hs
+++ b/src/Network/DNS.hs
@@ -152,6 +152,8 @@ queryRaw (Class cls) (Name name) qtype = withCResState $ \stptr -> do
             resetErrno
             reslen <- c_res_query stptr dn (fromIntegral cls) qtypeVal resptr max_msg_size
 
+            c_res_close stptr
+
             unless (reslen <= max_msg_size) $
                 fail "res_query(3) message size overflow"
 
@@ -188,6 +190,8 @@ sendRaw req = withCResState $ \stptr -> do
             resetErrno
             reslen <- c_res_send stptr reqptr (fromIntegral reqlen) resptr max_msg_size
 
+            c_res_close stptr
+
             unless (reslen <= max_msg_size) $
                 fail "res_send(3) message size overflow"
 
@@ -256,6 +260,8 @@ mkQueryRaw (Class cls) (Name name) qtype = withCResState $ \stptr -> do
             resetErrno
             reslen <- c_res_mkquery stptr dn (fromIntegral cls) qtypeVal resptr max_msg_size
 
+            c_res_close stptr
+
             unless (reslen <= max_msg_size) $
                 fail "res_mkquery(3) message size overflow"
 
diff --git a/src/Network/DNS/FFI.hs b/src/Network/DNS/FFI.hs
index cb6953b..2ee5278 100644
--- a/src/Network/DNS/FFI.hs
+++ b/src/Network/DNS/FFI.hs
@@ -68,3 +68,6 @@ foreign import capi safe "hs_resolv.h res_opt_set_use_dnssec" c_res_opt_set_use_
 -- int hs_res_mkquery(void *, const char *dname, int class, int type, unsigned char *req, int reqlen0);
 foreign import capi safe "hs_resolv.h hs_res_mkquery" c_res_mkquery :: Ptr CResState -> CString -> CInt -> CInt -> Ptr CChar -> CInt -> IO CInt
 
+-- void hs_res_close(void *);
+foreign import capi safe "hs_resolv.h hs_res_close" c_res_close :: Ptr CResState -> IO ()
+

Note that I also renamed res_nclose() to res_close() for compliance with the naming convention of other Haskell function that wrap the two C interfaces (I mean res_ninit()/res_init() etc.)

I also made a small test to demonstrate the memory leak.

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Network.DNS
import Control.Monad
import Control.Concurrent

main :: IO ()
main = foldM_ (const $ const $ do
                   v <- queryA (Name "www.google.com")
                   print v
                   threadDelay 1000
              ) () [1 .. 1000000]

This is a small program to quickly run queryA many times. When I compiled this (with ghc --make resolvTest.hs -fforce-recomp) and ran, I tested resident memory for the upstream and the patched variants of resolv using pidstat. Below are the results.

  • Upstream resolv

    $ pidstat -r $(pgrep -x resolvTest | sed 's/^/-p /') 2 30
    Linux 5.18.16-200.fc36.x86_64 (localhost.localdomain) 	13.08.2022 	_x86_64_	(8 CPU)
    
    22:57:36      UID       PID  minflt/s  majflt/s     VSZ     RSS   %MEM  Command
    22:57:38     1000    163856     11,00      0,00 1073978376   16272   0,10  resolvTest
    22:57:40     1000    163856     16,00      0,00 1073978376   16536   0,10  resolvTest
    22:57:42     1000    163856     10,00      0,00 1073978548   16536   0,10  resolvTest
    22:57:44     1000    163856      5,50      0,00 1073978548   16536   0,10  resolvTest
    22:57:46     1000    163856     15,50      0,00 1073978728   16800   0,10  resolvTest
    22:57:48     1000    163856      1,50      0,00 1073978728   16800   0,10  resolvTest
    22:57:50     1000    163856      8,00      0,00 1073978728   16800   0,10  resolvTest
    22:57:52     1000    163856      8,00      0,00 1073978728   16800   0,10  resolvTest
    22:57:54     1000    163856     15,00      0,00 1073978872   17064   0,11  resolvTest
    22:57:56     1000    163856      1,00      0,00 1073978872   17064   0,11  resolvTest
    22:57:58     1000    163856      8,00      0,00 1073979004   17064   0,11  resolvTest
    22:58:00     1000    163856      8,50      0,00 1073979004   17064   0,11  resolvTest   
    22:58:02     1000    163856      8,00      0,00 1073979136   17328   0,11  resolvTest
    22:58:04     1000    163856      8,00      0,00 1073979208   17380   0,11  resolvTest
    22:58:06     1000    163856      8,50      0,00 1073979340   17380   0,11  resolvTest
    22:58:08     1000    163856      8,00      0,00 1073979340   17380   0,11  resolvTest  
    22:58:10     1000    163856      8,00      0,00 1073979340   17380   0,11  resolvTest
    22:58:12     1000    163856      8,50      0,00 1073979472   17640   0,11  resolvTest
    22:58:14     1000    163856      7,50      0,00 1073979472   17640   0,11  resolvTest
    22:58:16     1000    163856      8,00      0,00 1073979604   17640   0,11  resolvTest
    22:58:18     1000    163856      8,50      0,00 1073979604   17640   0,11  resolvTest
    22:58:20     1000    163856      8,00      0,00 1073979844   17904   0,11  resolvTest
    22:58:22     1000    163856      8,00      0,00 1073979844   17904   0,11  resolvTest
    22:58:24     1000    163856      8,50      0,00 1073979844   17904   0,11  resolvTest
    22:58:26     1000    163856      8,00      0,00 1073979976   17904   0,11  resolvTest
    22:58:28     1000    163856      8,00      0,00 1073979976   18168   0,11  resolvTest
    22:58:30     1000    163856      9,00      0,00 1073980108   18168   0,11  resolvTest
    22:58:32     1000    163856      8,00      0,00 1073980108   18168   0,11  resolvTest
    22:58:34     1000    163856      8,00      0,00 1073980240   18168   0,11  resolvTest
    22:58:36     1000    163856      8,00      0,00 1073980240   18432   0,11  resolvTest
    Среднее:  1000    163856      8,48      0,00 1073979307   17382   0,11  resolvTest
    

    pidstat made 30 memory probes every 2 seconds, RSS was constantly growing from 16272 to 17382.

  • Patched resolv

    $ pidstat -r $(pgrep -x resolvTest | sed 's/^/-p /') 2 30
    Linux 5.18.16-200.fc36.x86_64 (localhost.localdomain) 	13.08.2022 	_x86_64_	(8 CPU)
    
    22:59:25      UID       PID  minflt/s  majflt/s     VSZ     RSS   %MEM  Command
    22:59:27     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:29     1000    164794      7,00      0,00 1073978112   16332   0,10  resolvTest  
    22:59:31     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:33     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:35     1000    164794      0,50      0,00 1073978112   16332   0,10  resolvTest
    22:59:37     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:39     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:41     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:43     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:45     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:47     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:49     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:51     1000    164794      0,50      0,00 1073978112   16332   0,10  resolvTest
    22:59:53     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:55     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:57     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    22:59:59     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:01     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:03     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:05     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:07     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:09     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:11     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:13     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:15     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:17     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:19     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:21     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:23     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    23:00:25     1000    164794      0,00      0,00 1073978112   16332   0,10  resolvTest
    Среднее:  1000    164794      0,27      0,00 1073978112   16332   0,10  resolvTest
    

    Now the memory doesn't grow: it's constant value 16332 during the probes.

I also managed to build the old interface (res_init(), res_query(), etc.) and checked that memory won't grow with the no-op hs_res_close().

If it looks good to you, I will add this improvements in a squashed commit.

@andreasabel
Copy link
Member

Yes, please go ahead.

I was also wondering if the call to c_res_close could be integrated into the combinator withCResState, using something like finally. This would be best practice, I suppose (withCResState provides a resource and also cleans it up after use).

@lyokha
Copy link
Collaborator Author

lyokha commented Aug 14, 2022

Yes, finally works fine, I tested it. The patch now is:

diff --git a/cbits/hs_resolv.h b/cbits/hs_resolv.h
index 37d486c..904b7d7 100644
--- a/cbits/hs_resolv.h
+++ b/cbits/hs_resolv.h
@@ -75,6 +75,14 @@ hs_res_query(struct __res_state *s, const char *dname, int class, int type, unsi
   return res_nquery(s, dname, class, type, answer, anslen);
 }
 
+inline static void
+hs_res_close(struct __res_state *s)
+{
+  assert(s);
+
+  res_nclose(s);
+}
+
 #else
 
 /* use non-reentrant API */
@@ -123,6 +131,11 @@ hs_res_query(void *s, const char *dname, int class, int type, unsigned char *ans
   return res_query(dname, class, type, answer, anslen);
 }
 
+inline static void
+hs_res_close(void *s)
+{
+}
+
 #endif
 
 #endif /* HS_RESOLV_H */
diff --git a/src/Network/DNS/FFI.hs b/src/Network/DNS/FFI.hs
index cb6953b..0490ac5 100644
--- a/src/Network/DNS/FFI.hs
+++ b/src/Network/DNS/FFI.hs
@@ -4,6 +4,7 @@
 module Network.DNS.FFI where
 
 import           Control.Concurrent.MVar
+import           Control.Exception       (finally)
 import           Foreign.C
 import           Foreign.Marshal.Alloc
 import           Foreign.Ptr
@@ -49,7 +50,7 @@ withCResState :: (Ptr CResState -> IO a) -> IO a
 withCResState act
   | resIsReentrant = allocaBytes (fromIntegral sizeOfResState) $ \ptr -> do
                          _ <- c_memset ptr 0 sizeOfResState
-                         act ptr
+                         act ptr `finally` c_res_close ptr
   | otherwise = withMVar resolvLock $ \() -> act nullPtr
 
 
@@ -68,3 +69,6 @@ foreign import capi safe "hs_resolv.h res_opt_set_use_dnssec" c_res_opt_set_use_
 -- int hs_res_mkquery(void *, const char *dname, int class, int type, unsigned char *req, int reqlen0);
 foreign import capi safe "hs_resolv.h hs_res_mkquery" c_res_mkquery :: Ptr CResState -> CString -> CInt -> CInt -> Ptr CChar -> CInt -> IO CInt
 
+-- void hs_res_close(void *);
+foreign import capi safe "hs_resolv.h hs_res_close" c_res_close :: Ptr CResState -> IO ()
+

Note that I added finally with c_res_close only for the reentrant interface (i.e. for n-functions).

@lyokha
Copy link
Collaborator Author

lyokha commented Aug 14, 2022

There is a logical problem with finally in withCResState though: it applies to th whole action but must apply to the part of action after c_res_opt_set_use_dnssec because it does not finalize ptr but rather res_ninit.

@andreasabel
Copy link
Member

There is a logical problem with finally in withCResState though: it applies to th whole action but must apply to the part of action after c_res_opt_set_use_dnssec because it does not finalize ptr but rather res_ninit.

Thanks for investigating. If that is the case, then it is better not to bundle it with withCResState, but keep your previous approach. Your comment, that c_res_close finalises c_res_init (via c_res_opt_set_use_dnssec), would be a great addition to the source code!

@lyokha
Copy link
Collaborator Author

lyokha commented Aug 14, 2022

@andreasabel, what if I introduce function withResInit in src/Network/DNS.hs with common initialization from queryRaw and alike, and finally close after this?

withResInit :: Ptr CResState -> IO a -> IO a
withResInit stptr act = do
   rc1 <- c_res_opt_set_use_dnssec stptr
   unless (rc1 == 0) $
       fail "res_init(3) failed"
   resetErrno
   act `finally` c_res_close stptr

So that queryRaw and alike functions will look as

queryRaw :: Class -> Name -> Type -> IO BS.ByteString
queryRaw (Class cls) (Name name) qtype = withCResState $ \stptr -> do
    allocaBytes max_msg_size $ \resptr -> do
        _ <- c_memset resptr 0 max_msg_size
        BS.useAsCString name $ \dn -> do

            withResInit stptr $ do

                reslen <- c_res_query stptr dn (fromIntegral cls) qtypeVal resptr max_msg_size

                unless (reslen <= max_msg_size) $
                    fail "res_query(3) message size overflow"

                errno <- getErrno

                when (reslen < 0) $ do
                    unless (errno == eOK) $
                        throwErrno "res_query"

                    fail "res_query(3) failed"

                BS.packCStringLen (resptr, fromIntegral reslen)

Now finalization goes after c_res_init only when it finished succesfully.

@andreasabel
Copy link
Member

@andreasabel, what if I introduce function withResInit in src/Network/DNS.hs with common initialization from queryRaw and alike, and finally close after this?

Excellent structure!

Copy link
Member

@andreasabel andreasabel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great now!

@lyokha
Copy link
Collaborator Author

lyokha commented Aug 15, 2022

Not yet! I just pushed another protection against asynchronous exceptions: see details in the commit.

fail "res_init(3) failed"
resetErrno
act `finally` c_res_close stptr
withCResInit stptr act = flip finally (c_res_close stptr) $ do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused now. Shouldn't c_res_close only be called after c_res_opt_set_use_dnssec was successful?
I thought this was the reason not to integrate it directly into withCResState.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c_res_close is actually safe to call inside withCResState, but this is only lucky coincidence because we do

_ <- c_memset ptr 0 sizeOfResState

inside withCResState after allocation of the state pointer. In this case (all fields, e.g. pointers, etc., are zeros), c_rec_close is effectively no-op and thus safe. But logically, it must be called after succesful res_ninit().

By wrapping the whole withCResInit in finally $ cres_close ptr I use the same lucky assumption as could be used for withResCState, but with withCResInit this does not expand too far beyond the place of call to res_ninit().

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... not exactly no-op, see glibc implementation here, but at least statp->nscount will be 0 and no free happen (inside function __res_iclose(). Though I worry about possible call to __close_nocacnel_nostatus() (because statp->_vsocks is 0) and __resolv_conf_detach()

Copy link
Contributor

@phadej phadej Aug 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You really should structure this using bracket and not finally.

I think that what @andreasabel was asking.

If res_ninit fails, one should assume there isn't anything to close/ cleanup. Docs don't say either way, especially they don't say that it's safe to call res_close on pointer which is has failed to res_ninit. One should not look into documentation to figure out whether it's safe or not, as there is no contract (= docs) it won't change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... so probably it's safer to keep to the 2nd commit, though it makes possible to leak memory in environments with asynchronous exceptions

Copy link
Contributor

@phadej phadej Aug 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In bracket initialization is run with asynchronous exception masked. I don't understand how

bracket init c_res_close $ \sptr' -> act sptr' where
  init = do
    rc1 <- c_res_opt_set_use_dnssec stptr
    unless (rc1 == 0) $ fail "res_init(3) failed"
    resetErrno
    return sptr

would leak.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that bracket won't leak, just tested with this:

withCResInit :: Ptr CResState -> IO a -> IO a
withCResInit stptr act = bracket
     (do
         rc1 <- c_res_opt_set_use_dnssec stptr
         unless (rc1 == 0) $
             fail "res_init(3) failed"
         return ()
     )
     (const $ c_res_close stptr)
     (const $ do
         resetErrno
         act
     )

I'll commit this if you don't mind against style

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return () was excessive after unless but I already committed

@andreasabel
Copy link
Member

@lyokha: I reverted some of your cosmetic changes (redundant do, indentation) to keep the diff minimal.
From your side, is this PR ready?

src/Network/DNS/FFI.hs Outdated Show resolved Hide resolved
@lyokha
Copy link
Collaborator Author

lyokha commented Aug 17, 2022

@lyokha: I reverted some of your cosmetic changes (redundant do, indentation) to keep the diff minimal. From your side, is this PR ready?

Ok, I think the PR is ready, the only little concern is that withCResInit return () in the initializer part - this is redundant as unless does this. I don't insist on deletion of this though.

Copy link
Member

@andreasabel andreasabel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, from my side, this PR is finished as well. I think the final result is very nice, giving more structure to the existing code.

src/Network/DNS/FFI.hs Outdated Show resolved Hide resolved
@andreasabel
Copy link
Member

@phadej , this is now over to you, unless you give me hackage rights, in which case I would release this.

@andreasabel
Copy link
Member

Ping @phadej : please add me as hackage maintainer.

@dminuoso
Copy link
Contributor

@andreasabel You could just start the second step in https://wiki.haskell.org/Taking_over_a_package procedure, a month is reasonable time in my mind.

lyokha added a commit to lyokha/ngx-export-tools-extra that referenced this pull request Sep 27, 2022
@andreasabel andreasabel added this to the 0.2.0.0 milestone Jan 29, 2023
@andreasabel
Copy link
Member

@Mergifyio rebase

lyokha and others added 6 commits March 28, 2023 12:59
Linux man 3 resolver says:
    Every call to res_ninit() requires a corresponding call to
    res_nclose() to free memory allocated by res_ninit() and subsequent
    calls to res_nquery().
withCResInit which makes sure that allocated resources get correctly
released by wrapping actions following a successful call to c_res_init
in finally handler.
This protects against asynchronous exceptions. If an asynchronous
exception happens somewhere inside of

    rc1 <- c_res_opt_set_use_dnssec stptr
    unless (rc1 == 0) $
        fail "res_init(3) failed"
    resetErrno

then it may leak memory after successful call to res_ninit() inside
c_res_opt_set_use_dnssec.

Note that it is safe to run c_res_close stptr if all fields of stptr
are memzeroed (which is our case before the call to res_ninit()): this
means that wrapping the entire body of withCResInit is safe even if an
asynchronous exception occurs before the call to res_ninit().
@mergify
Copy link

mergify bot commented Mar 28, 2023

rebase

✅ Branch has been successfully rebased

@andreasabel
Copy link
Member

andreasabel commented Mar 28, 2023

Rebased onto master to test with GHC 9.6.1 (latest CI).

PR should be SQUASHED.

CI passes. @lyokha, are you happy with this PR in this form? Then we can merge it (by squashing).

@andreasabel andreasabel added the pr: squash me PR should be squashed upon merge label Mar 28, 2023
@lyokha
Copy link
Collaborator Author

lyokha commented Mar 28, 2023

@andreasabel I think that it's ok. I will PR the configure warning fix after this.

@lyokha
Copy link
Collaborator Author

lyokha commented Mar 28, 2023

@andreasabel Could you do the merge? I am not familiar with Mergify at all :(

@andreasabel andreasabel merged commit 9064226 into haskell-hvr:master Mar 28, 2023
@andreasabel
Copy link
Member

@andreasabel Could you do the merge? I am not familiar with Mergify at all :(

Sure, did it.
Actually, in this case there is no Mergify involved, just hit the "Squash & merge" button. ;-)

@hvr
Copy link
Collaborator

hvr commented Jan 10, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr: squash me PR should be squashed upon merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants