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

ClassCastException when storing ConcurrentSkipListSet with custom Comparator in EclipseStore #352

Open
AbdelkaderR opened this issue Dec 22, 2024 · 1 comment

Comments

@AbdelkaderR
Copy link

AbdelkaderR commented Dec 22, 2024

Environment Details

  • EclipseStore Version: 2.1.0
  • JDK version: 17.0.9
  • OS: Windows 11
  • Used frameworks: JUnit 5

Describe the bug

ClassCastException when trying to store/retrieve Records in a ConcurrentSkipListSet using EclipseStore.

Error message:

java.lang.ClassCastException: class Record cannot be cast to class java.lang.Comparable

The issue is consistently reproducible.

To Reproduce

  1. Create a Record class with a timestamp field
  2. Create a TestData class with a Map<String, NavigableSet>
  3. Use ConcurrentSkipListSet with a custom Comparator
  4. Try to store and retrieve data using EclipseStore
  5. Get ClassCastException during storage operation

Expected behavior

Records should be stored and retrieved successfully in the sorted set without any ClassCastException.

Additional context

The issue seems related to serialization/deserialization of the ConcurrentSkipListSet with a custom Comparator in EclipseStore. Implementing Comparable interface on Record class could be potential workarounds.

Code

package eclipse.store.test;
import ch.qos.logback.classic.Level;
import org.eclipse.store.storage.embedded.types.EmbeddedStorage;
import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;
class EclipseStoreTest {
    private static final Logger logger = LoggerFactory.getLogger(EclipseStoreTest.class);
    private static final String STORAGE_PATH = "target/test-storage";
    static class Record /*implements Comparable<Record>*/ {
        private final Long timestamp;
        public Record(Long timestamp) {
            this.timestamp = timestamp;
        }
        public Long getTimestamp() {
            return timestamp;
        }
//        @Override
//        public int compareTo(Record other) {
//            return Long.compare(this.timestamp, other.timestamp);
//        }
        @Override
        public String toString() {
            return "Record{timestamp=" + timestamp + '}';
        }
    }
    static class RecordComparator implements Comparator<Record> {
        @Override
        public int compare(Record record1, Record record2) {
            int timestampComparison = Long.compare(record1.getTimestamp(), record2.getTimestamp());
            return timestampComparison;
        }
    }
    static class TestData {
        private Map<String, NavigableSet<Record>> data;
        public TestData() {
            this.data = new HashMap<>();
        }
        public void addRecord(String connector, Record r) {
            data.computeIfAbsent(connector, k -> new ConcurrentSkipListSet<>(new RecordComparator())).add(r);
        }
        public NavigableSet<Record> getRecords(String connector) {
            return data.getOrDefault(connector, new ConcurrentSkipListSet<>(new RecordComparator()));
        }
    }
    @Test
    void storeInitialData() {
        EmbeddedStorageManager storage = EmbeddedStorage.start(Paths.get(STORAGE_PATH));
        try {
            TestData testData = new TestData();
            testData.addRecord("set1", createRecord(Instant.now().minus(Duration.ofDays(1)).toEpochMilli()));
            storage.setRoot(testData);
            storage.storeRoot();
        } finally {
            storage.shutdown();
        }
    }
    @Test
    void readData() {
        TestData updatedData = loadData();
        logger.info("Records: {}", updatedData.getRecords("set1"));
    }
    private void persistData(TestData data) {
        EmbeddedStorageManager storage = EmbeddedStorage.start(Paths.get(STORAGE_PATH));
        try {
            storage.setRoot(data);
            storage.storeRoot();
        } finally {
            storage.shutdown();
        }
    }
    private TestData loadData() {
        EmbeddedStorageManager storage = EmbeddedStorage.start(Paths.get(STORAGE_PATH));
        try {
            TestData data = (TestData) storage.root();
            if (data == null) {
                logger.error("Data could not be loaded. Root is null.");
                return new TestData();
            }
            return data;
        } finally {
            storage.shutdown();
        }
    }
    private Record createRecord(long timestamp) {
        return new Record(timestamp);
    } 
}
@Oscar-21
Copy link
Contributor

Oscar-21 commented Jan 1, 2025

I think it's likely that the serializer needs to be aware of this comparator on the Record class at compile time by defining it, as you've suggested, on the Record class itself. If you're passing a custom comparator via the constructor, that could vary at runtime(imagineRecordComparatorFoo extends RecordComparator), and I'm not sure that we would expect that the serializer would persist information about whether you passed a RecordComparatorFoo or RecordComparator to the ConcurrentSkipListSet<>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants