Opened 4 years ago

Last modified 4 years ago

#9765 new bug

Strange behavior of GC under ghci

Reported by: remdezx Owned by:
Priority: normal Milestone:
Component: GHCi Version: 7.8.3
Keywords: Cc: hvr
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: Incorrect result at runtime Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

Releasing the result of newForeignPtr nullFunPtr nullPtr end in core dump, due to the fact that finalizer function is set to null pointer.

When I run something like this in GHCI:

> import System.Mem
> import Foreign.Ptr 
> import Foreign.ForeignPtr
> import System.IO.Unsafe
> import qualified Data.Map as Map
> a <- return $ Map.singleton 1 (unsafePerformIO $ newForeignPtr nullFunPtr nullPtr)
Loading package array-0.5.0.0 ... linking ... done.
Loading package deepseq-1.3.0.2 ... linking ... done.
Loading package containers-0.5.5.1 ... linking ... done.
> print a
fromList [(1,0x0000000000000000)]
> performGC
> ^D
Leaving GHCi.
[1]    3782 segmentation fault (core dumped)  ghci

it wont crash until exit from ghci which is correct. But if I do something similar but using let binding it will crash even if variable a didn't lose its scope

> import System.Mem
> import Foreign.Ptr 
> import Foreign.ForeignPtr
> import System.IO.Unsafe
> import qualified Data.Map as Map
> let a = Map.singleton 1 (unsafePerformIO $ newForeignPtr nullFunPtr nullPtr)
Loading package array-0.5.0.0 ... linking ... done.
Loading package deepseq-1.3.0.2 ... linking ... done.
Loading package containers-0.5.5.1 ... linking ... done.
> print a
fromList [(1,0x0000000000000000)]
[1]    3842 segmentation fault (core dumped)  ghci

Why is there a difference between doing it with do notation and with let binding?

I also expected that if I rebind variable a it will lose it's scope and will be released but it is not (see below)

> import System.Mem
> import Foreign.Ptr 
> import Foreign.ForeignPtr
> import System.IO.Unsafe
> import qualified Data.Map as Map
> a <- return $ Map.singleton 1 (unsafePerformIO $ newForeignPtr nullFunPtr nullPtr)
Loading package array-0.5.0.0 ... linking ... done.
Loading package deepseq-1.3.0.2 ... linking ... done.
Loading package containers-0.5.5.1 ... linking ... done.
> print a
fromList [(1,0x0000000000000000)]
> a <- return () -- rebinding varable a, it is no longer used
> performGC
>  -- no crash, varaible a not released

Change History (3)

comment:1 Changed 4 years ago by rwbarton

Why is there a difference between doing it with do notation and with let binding?

Well, this part is easy: the let binding is generalized over the type of the numeric literal 1, so that a is really a function that constructs a new Map and a new ForeignPtr each time it is called (evaluated). The result in the do notation is not generalized, and is really a Map.

comment:2 Changed 4 years ago by nomeata

I did some analysis in http://stackoverflow.com/a/26734895/946226 and it is the ClosureEnv in the Linker module that keeps hold of the bound HValues, even when the binding is shadowed. The ClosureEnv is a NameEnv, so its keys are Uniques, and obviously new bindings get new uniques, so the interpreter code should, upon adding a new binding to it, check if some other names are out of scope now and remove them from the ClosureEnv. This would allow them to be GC’ed (if nothing else references them, of course).

comment:3 Changed 4 years ago by remdezx

@rwbarton, thanks for explaining the difference. I didn't know that!

Note: See TracTickets for help on using tickets.