Repository: ultrajson/ultrajson (language: python)

Issue:
Memory leak in ujson.dumps ?
Hello, 

I was facing a memory leak issue in a script based on the web-server [sanic](https://sanic.readthedocs.io/). After experimenting around and trying to build a minimal example, I arrived to the conclusion, that the memory leak happens in `dumps()` of `ujson`. 
A minimal example would be: 

```python
from ujson import dumps
for _ in range(10000):
    big_dict = {i: {j: [6666666, 7777777, 8888888] for j in range(30)}
                for i in range(111100, 111400)}
    dumps(big_dict)
    print("Done", _)
```
and simply observe the memory usage growing in `top` (app. 2MB per seconds on my laptop).
This memory leak does not happen if one uses `dumps()`from the standard ''json'' module instead.

**EDIT 1**: Notice also that this does not happen if the `big_dict` is defined **outside** (and thus only once) the `for` loop. This might indicate some non-released reference to the dict object in the C code (but I'm completely newbie in C).



Discussion / hints:
This seems to be an issue with reference counting non-string keys.  Here's a more minimal example:
```python
k = 1024
print(sys.getrefcount(k))
dumps({k: 1})
print(sys.getrefcount(k))
```
(I'd expect reference counts to stay the same between invocations, e.g. try setting `k` to a string or using `json.dumps`)

While debugging this I also noticed that if I hit <kbd>Ctl+C</kbd> in the above loop Python would sometimes get terminated with `SIGSEGV` .  I can reliably reproduce this via:
```python
class Obj:
    def __str__(self):
        raise KeyboardInterrupt

dumps({Obj(): 1})
```
Hope that helps!
Any progress on this one? I've been trying to trace a strange leak in one of my own projects, but until recently failed to realize it was `ujson`. A quick search of "ujson memory leak" yields a list of related Github issues.
https://github.com/ultrajson/ultrajson/blob/master/python/objToJSON.c#L267

Maybe the code here cause the memory leak issue?
The GET_TC(tc)->itemName is pointed to the new generated PyObject_Str object, but the old object refcount is not handled.

Fix patch:
diff --git a/python/objToJSON.c b/python/objToJSON.c
index 743e6f29..10970d37 100644
--- a/python/objToJSON.c
+++ b/python/objToJSON.c
@@ -254,15 +254,13 @@ static int Dict_iterNext(JSOBJ obj, JSONTypeContext *tc)
       return 1;
     }
 
+    itemNameTmp = GET_TC(tc)->itemName;
     GET_TC(tc)->itemName = PyObject_Str(GET_TC(tc)->itemName);
+    Py_DECREF(itemNameTmp);
     itemNameTmp = GET_TC(tc)->itemName;
     GET_TC(tc)->itemName = PyUnicode_AsUTF8String (GET_TC(tc)->itemName);
     Py_DECREF(itemNameTmp);
   }
-  else
-  {
-    Py_INCREF(GET_TC(tc)->itemName);
-  }
   PRINTMARK();
   return 1;
 }


Test patch:
diff --git a/tests/test_ujson.py b/tests/test_ujson.py
index b0b39479..69d76bc8 100644
--- a/tests/test_ujson.py
+++ b/tests/test_ujson.py
@@ -239,11 +239,11 @@ def test_encode_dict_values_ref_counting():
 @pytest.mark.skipif(
     hasattr(sys, "pypy_version_info"), reason="PyPy uses incompatible GC"
 )
-def test_encode_dict_key_ref_counting():
+@pytest.mark.parametrize("key", ["key", b"key", 1, True, None])
+def test_encode_dict_key_ref_counting(key):
     import gc
 
     gc.collect()
-    key = "key"
     data = {key: "abc"}
     ref_count = sys.getrefcount(key)
     ujson.dumps(data)