From dbf39434d0419de64406b185ad3f2808089dbdac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@ericsson.com>
Date: Fri, 2 Aug 2024 10:56:06 +0200
Subject: [PATCH] fix

---
 .gitlab-ci.yml          | 20 +++++++++++++++++++-
 README.md               |  5 ++---
 docs/usage.rst          |  7 +++++++
 tempsdb/chunks/base.pxd |  3 ++-
 tempsdb/chunks/base.pyx | 15 +++++++++++----
 tempsdb/database.pyx    |  5 ++++-
 tempsdb/series.pyx      | 11 ++++++++---
 7 files changed, 53 insertions(+), 13 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0ec9352..d366216 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -19,9 +19,27 @@ stages:
   script:
     - python -m coverage run -m nose2 -vv -F
     - python -m coverage report
-
+  coverage: /TOTAL.*\s+(\d+\%)/
 
 
 test_python38:
   extends: .test_python
   image: python:3.8
+
+
+
+test_python39:
+  extends: .test_python
+  image: python:3.9
+
+
+
+test_python310:
+  extends: .test_python
+  image: python:3.10
+
+
+
+test_python311:
+  extends: .test_python
+  image: python:3.11
diff --git a/README.md b/README.md
index bb70449..3a26786 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,8 @@
 [![PyPI version](https://badge.fury.io/py/tempsdb.svg)](https://badge.fury.io/py/tempsdb)
 [![PyPI](https://img.shields.io/pypi/implementation/tempsdb.svg)](https://pypi.python.org/pypi/tempsdb)
 [![Documentation Status](https://readthedocs.org/projects/tempsdb/badge/?version=latest)](http://tempsdb.readthedocs.io/en/latest/?badge=latest)
-[![Maintainability](https://api.codeclimate.com/v1/badges/657b03d115f6e001633c/maintainability)](https://codeclimate.com/github/smok-serwis/tempsdb/maintainability)
-[![Test Coverage](https://api.codeclimate.com/v1/badges/a0ff30771c71e43e8149/test_coverage)](https://codeclimate.com/github/smok-serwis/tempsdb/test_coverage)
-[![Build Status](https://travis-ci.com/smok-serwis/tempsdb.svg)](https://travis-ci.com/smok-serwis/tempsdb)
+[![Build status](https://git.dms-serwis.com.pl/smokserwis/tempsdb/badges/develop/pipeline.svg)](https://git.dms-serwis.com.pl/smokserwis/tempsdb)
+[![coverage report](https://git.dms-serwis.com.pl/smokserwis/tempsdb/badges/develop/coverage.svg)](https://git.dms-serwis.com.pl/smokserwis/tempsdb/-/commits/develop)
 [![Wheel](https://img.shields.io/pypi/wheel/tempsdb.svg)](https://pypi.org/project/tempsdb/)
 [![License](https://img.shields.io/pypi/l/tempsdb)](https://github.com/smok-serwis/tempsdb)
 
diff --git a/docs/usage.rst b/docs/usage.rst
index 4c3876b..d8015cb 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -54,3 +54,10 @@ Logging
 tempsdb will log when opening and closing series. To prevent this from happening, just call:
 
 .. autofunction:: tempsdb.database.disable_logging
+
+Failing to close things
+-----------------------
+
+Please be keen on manually closing things that are no longer necessary.
+Most destructors check for that and will complain if you forget to manually
+close anything.
\ No newline at end of file
diff --git a/tempsdb/chunks/base.pxd b/tempsdb/chunks/base.pxd
index bd446f5..e2f3dd4 100644
--- a/tempsdb/chunks/base.pxd
+++ b/tempsdb/chunks/base.pxd
@@ -11,6 +11,7 @@ cdef class Chunk:
     cdef:
         TimeSeries parent
         readonly str path
+        bint is_object_closed
         readonly unsigned long long min_ts
         readonly unsigned long long max_ts
         readonly unsigned int block_size
@@ -28,7 +29,7 @@ cdef class Chunk:
     cpdef bytes get_value_at(self, unsigned int index)
     cpdef bytes get_slice_of_piece_at(self, unsigned int index, unsigned int start, unsigned int stop)
     cpdef bytes get_slice_of_piece_starting_at(self, unsigned int index, unsigned int start)
-    cpdef int get_byte_of_piece(self, unsigned int index, unsigned int byte_index) except -1
+    cpdef unsigned char get_byte_of_piece(self, unsigned int index, unsigned int byte_index) except -1
     cpdef unsigned int find_left(self, unsigned long long timestamp)
     cpdef unsigned int find_right(self, unsigned long long timestamp)
     cdef object open_file(self, str path)
diff --git a/tempsdb/chunks/base.pyx b/tempsdb/chunks/base.pyx
index 5a90cb6..ce005d6 100644
--- a/tempsdb/chunks/base.pyx
+++ b/tempsdb/chunks/base.pyx
@@ -7,7 +7,7 @@ import mmap
 import warnings
 
 from .gzip cimport ReadWriteGzipFile
-from ..exceptions import Corruption, StillOpen
+from ..exceptions import Corruption, StillOpen, InvalidState
 from ..series cimport TimeSeries
 
 
@@ -182,6 +182,7 @@ cdef class Chunk:
         self.page_size = page_size
         self.parent = parent
         self.closed = False
+        self.is_object_closed = False
         self.path = path
         self.file = self.open_file(path)
         self.file_lock_object = None
@@ -213,7 +214,7 @@ cdef class Chunk:
 
         self.after_init()
 
-    cpdef int get_byte_of_piece(self, unsigned int index, unsigned int byte_index) except -1:
+    cpdef unsigned char get_byte_of_piece(self, unsigned int index, unsigned int byte_index) except -1:
         """
         Return a particular byte of given element at given index.
         
@@ -325,8 +326,12 @@ cdef class Chunk:
 
     cdef int sync(self) except -1:
         """
-        Synchronize the mmap
+        Synchronize the mmap.
+        
+        :raises InvalidState: object is already closed
         """
+        if self.is_object_closed:
+            raise InvalidState('Cannot sync a closed chunk!')
         self.mmap.flush()
         return 0
 
@@ -426,10 +431,12 @@ cdef class Chunk:
         self.sync()
         self.mmap.close()
         self.file.close()
+        self.closed = True
+        self.is_object_closed = True
         return 0
 
     def __del__(self) -> None:
-        if self.closed:
+        if self.is_object_closed or self.closed:
             return
         warnings.warn('You forgot to close a Chunk')
         self.close()
diff --git a/tempsdb/database.pyx b/tempsdb/database.pyx
index d5b5495..f210b1f 100644
--- a/tempsdb/database.pyx
+++ b/tempsdb/database.pyx
@@ -27,6 +27,8 @@ cdef class Database:
 
     If you forget to, the destructor will do that instead and emit a warning.
 
+    This is internally synchronized thanks to a reentrant lock.
+
     :param path: path to the directory with the database
     :raises DoesNotExist: database does not exist, use `create_database`
 
@@ -35,6 +37,7 @@ cdef class Database:
     """
     def __init__(self, str path):
         if not os.path.isdir(path):
+            self.closed = True
             raise DoesNotExist('Database does not exist')
 
         if not os.path.isdir(os.path.join(path, 'varlen')):
@@ -393,6 +396,7 @@ cdef class Database:
         cdef:
             TimeSeries series
             VarlenSeries var_series
+        self.closed = True
         with self.lock:
             for series in self.open_series.values():
                 series.close()  # because already closed series won't close themselves
@@ -400,7 +404,6 @@ cdef class Database:
             for var_series in self.open_varlen_series.values():
                 var_series.close(True)
             self.open_varlen_series = {}
-        self.closed = True
         if self.mpm_handler is not None:
             self.mpm_handler.cancel()
             self.mpm_handler = None
diff --git a/tempsdb/series.pyx b/tempsdb/series.pyx
index 5f1603b..47ba142 100644
--- a/tempsdb/series.pyx
+++ b/tempsdb/series.pyx
@@ -111,6 +111,7 @@ cdef class TimeSeries:
         self.path = path
 
         if not os.path.isdir(self.path):
+            self.closed = True
             raise DoesNotExist('Chosen time series does not exist')
 
         cdef:
@@ -130,6 +131,7 @@ cdef class TimeSeries:
             self.metadata = metadata.get('metadata')
             self.gzip_level = metadata.get('gzip_level', 0)
         except (OSError, ValueError, KeyError) as e:
+            self.closed = True
             raise Corruption('Corrupted series: %s' % (e, )) from e
 
         self.open_chunks = {}       # tp.Dict[int, Chunk]
@@ -137,6 +139,7 @@ cdef class TimeSeries:
                                     #: timestamp, is_direct, is_gzip
 
         if not len(files):
+            self.closed = True
             raise Corruption('Empty directory!')
         elif len(files) == 1:
             # empty series
@@ -156,12 +159,14 @@ cdef class TimeSeries:
                 try:
                     self.chunks.append((int(filename), is_direct, is_gzip))
                 except ValueError:
+                    self.closed = True
                     raise Corruption('Detected invalid file "%s"' % (filename, ))
             self.chunks.sort()
 
             try:
                 last_chunk_name, is_direct, is_gzip = self.chunks[-1]
             except IndexError as e:
+                self.closed = True
                 raise Corruption('Corrupted series: %s' % (e, )) from e
             self.last_chunk = self.open_chunk(last_chunk_name, is_direct, is_gzip)
             self.last_entry_ts = self.last_chunk.max_ts
@@ -274,11 +279,11 @@ cdef class TimeSeries:
         """
         cdef:
             Chunk chunk
-            list open_chunks
+            list l_open_chunks
         if self.closed:
             return 0
-        open_chunks = list(self.open_chunks.values())
-        for chunk in open_chunks:
+        l_open_chunks = list(self.open_chunks.values())
+        for chunk in l_open_chunks:
             chunk.close(True)
         if self.mpm is not None:
             self.mpm.cancel()
-- 
GitLab