nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.54k stars 1.47k forks source link

Object destructor not called when reassigning after a try-except block. #24103

Open darkestpigeon opened 1 month ago

darkestpigeon commented 1 month ago

Description

Object destructor not called when reassigning after a try-except block.

Minimal example:

import std/os

type MyObj = object
  data: pointer
  size: int

proc initMyObj(size: int): MyObj =
  assert size > 0
  result.data = allocShared0(size)
  result.size = size

  # imitate work
  let p = cast[ptr UncheckedArray[uint8]](result.data)
  for i in 0..<size:
    p[i] = i.uint8

proc `=destroy`(x: MyObj) =
  if not x.data.isNil:
    deallocShared(x.data)

proc `=wasMoved`(x: var MyObj) =
  x.data = nil
  x.size = 0

proc `=copy`(x: var MyObj, y: MyObj) {.error.}

proc main() =
  while true:
    var obj: MyObj
    try:
      obj = initMyObj(10_000_000)
    except:
    # except FloatOverflowError: also causes a leak, although it is never raised
      break

    # discard obj

    obj = MyObj()
    sleep(20)

    echo getOccupiedMem()

main()

Uncommenting the discard obj line removes the issue.

Nim Version

Both on

Nim Compiler Version 2.0.8 [Linux: amd64]
Compiled at 2024-07-03
Copyright (c) 2006-2023 by Andreas Rumpf

git hash: 5935c3bfa9fec6505394867b23510eb5cbab3dbf
active boot switches: -d:release

and

Nim Compiler Version 2.1.99 [Linux: amd64]
Compiled at 2024-09-12
Copyright (c) 2006-2024 by Andreas Rumpf

git hash: 793cee4de1934fd1f6271cf5fed46f01c5abb19b
active boot switches: -d:release

Current Output

10223616                                                                                  
20447232                                                                                  
30670848                                     
40894464                                                                                  
51118080                                                                                                                                                                            
61341696                                                                                  
71565312                                                                                                                                                                            
81788928                                     
92012544                                     
102236160                                                                                                                                                                           
112459776                                                                                 
122683392                                                                                                                                                                           
132907008                                                                                                                                                                           
143130624                                                                                                                                                                           
153354240
...

Expected Output

0                                            
0                                            
0                                                                                                                                                                                   
0                                                                                         
0                                                                                                                                                                                   
0                                                                                                                                                                                   
0                                                                                                                                                                                   
0                                                                                                                                                                                   
0                                                                                         
0                                                                                         
0                                                                                         
0                                                                                         
0                                                                                         
0                                                                                         
0                                                                                         
0    
...

Known Workarounds

In the example above, uncommenting the discard obj line removes the issue.

Additional Information

No response

forchid commented 4 weeks ago

It's also ok if access obj before try:, such as,

The test source

import std/os

type MyObj = object
  data: pointer
  size: int

proc initMyObj(size: int): MyObj =
  assert size > 0
  result.data = allocShared0(size)
  result.size = size

  # imitate work
  let p = cast[ptr UncheckedArray[uint8]](result.data)
  for i in 0..<size:
    p[i] = i.uint8

proc `=destroy`(x: MyObj) =
  if not x.data.isNil:
    deallocShared(x.data)

proc `=wasMoved`(x: var MyObj) =
  x.data = nil
  x.size = 0

proc `=copy`(x: var MyObj, y: MyObj) {.error.}

proc main() =
  while true:
    var obj: MyObj
    discard obj
    try:
      obj = initMyObj(10_000_000)
    except:
    # except FloatOverflowError: also causes a leak, although it is never raised
      break

    # discard obj

    obj = MyObj()
    sleep(20)

    echo getOccupiedMem()

main()

The test result

>except
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
forchid commented 4 weeks ago

@darkestpigeon I find that this problem doesn't exist in nim-1.6.20 with --mm:orc or --mm:orc, but memory leak 48 bytes with --mm:refc.

Test test source

import std/os

type MyObj = object
  data: pointer
  size: int

proc initMyObj(size: int): MyObj =
  assert size > 0
  result.data = allocShared0(size)
  result.size = size

  # imitate work
  let p = cast[ptr UncheckedArray[uint8]](result.data)
  for i in 0..<size:
    p[i] = i.uint8

proc `=destroy`(x: var MyObj) = # Using `var` in nim-1.6.20
  if not x.data.isNil:
    deallocShared(x.data)

proc `=wasMoved`(x: var MyObj) =
  x.data = nil
  x.size = 0

proc `=copy`(x: var MyObj, y: MyObj) {.error.}

proc main() =
  while true:
    var obj: MyObj
    try:
      obj = initMyObj(10_000_000)
    except:
    # except FloatOverflowError: also causes a leak, although it is never raised
      break

    # discard obj

    obj = MyObj()
    sleep(20)

    echo getOccupiedMem()

main()

The test resul

>except
0
0
0
0
0
0
0
0
0
0
...