Wyciek pamięci pomiędzy wątkami interceptora i połączenia z bazą

0

Cześć,

Od dłuższego czasu mam pewien problem do rozwiązania. Od razu napiszę, że mój stack technologiczny to Java 1.8 oraz Spring 2. Opiszę mój problem następująco:
Posiadam webservice w Springu, który po wywołaniu przechwytuje w interceptorze wejściowym wartości odczytywane w nagłówkach Soap-owych. Następnie serwis, do którego wysyłam żądanie wykonuje logikę biznesową i wysyła je do innego/innych serwisów nie oznaczonych adnotacją Webservice. Jak wiadomo w takich serwisach nie mogę odczytać już wartości siedzących w kontekście po wywołaniu Webservice. Muszę jednak przekazać jakoś te wartości z interceptora wejściowego do interceptora wyjściowego i tutaj jest problem. W serwisie pośrednim (tym, do którego podpięty jest interceptor wyjściowy) wykonywany jest strzał do bazy w nowym wątku. Aktualnie ten problem rozwiązuje poprzez utworzenie obiektu ThreadLocal w interceptorze wejściowym, odczytanie go przed utworzeniem nowego wątku, po czym utworzenie kolejnego obiektu typu ThreadLocal i odczytanie go w interceptorze wyjściowym. Niestety problem z ThreadLocal jest taki, że powoduje wycieki pamięci. Czy jest jakiś sposób aby zrobić to inaczej? Proszę o pomoc.

3

Pytanie całkiem serio - po co trzymać coś w ThreadLocal? Nie lepiej opakować to w jakieś Pojo w stylu:

public class MyPojoWrapper {
   private final Pojo myOriginalData;
   private final AdditionalData dataFromHeaders;

   // ...
}

I po prostu tak skomponowany obiekt przekazywać po ludzku?
Pytam, bo po prostu nie widzę powodu, dla którego miałbym tego typu dane wrzucać do ThreadLocal, bo javowe ThreadLocal jest ograniczone do Javowego wątku - a w takim przypadku nie masz żadnej gwarancji jak zachowa się Spring (czy sobie wykorzysta ten wątek czy go zniszczy).

0

Pewnie, że byłoby lepiej. Jednak problem jest bardziej złożony. Pomiędzy wywołaniem ostatniego serwisu są wywoływane inne, nie mówiąc, że jest ich dużo i ręczne przekazywanie jest mało praktyczne, nie mówiąc o potrzebie robienia tego w przypadku dodania nowych funkcjonalności. Stąd taki problem.

1

Może jakbyś napisał, że nie chce ci się tego przepisywać - to wtedy byłbym w stanie zrozumieć twoje tłumaczenie :)

Natomiast w tej chwili:

  • ręcznie tworzysz zmienną w ThreadLocal
  • ręcznie musisz ją odczytać przed jakąkolwiek operacją, która zadzieje się w innym wątku
  • ręcznie musisz ją odczytać na interceptorze wyjściowym
  • w przypadku nowych funkcjonalności będziesz musiał te operacje również ręcznie ogarnąć

Więc argument, że to jest jakoś mało praktyczne raczej do mnie nie trafia. Opakowanie odpowiedzi we własnego response wrappera powinno być dosyć transparentne dla działania aplikacji.

0

To nie tak, że nie chce mi się tego przepisywać, po prostu za duża ingerencja w kod programu. Tych serwisów mało nie jest a relacje pomiędzy nimi nie są tak oczywiste. Aktualnie po prostu odczytuje to w interceptorze wejściowym raz, wczytuje do ThreadLocal i wtedy jestem w stanie wyciągnąć to w serwisie, do którego docelowo ma to trafiać, tam muszę zrobić myk i przekazać to dalej do interceptora wyjściowego tworząc nowy obiekt ThreadLocal w nowym wątku, kopiując do niego wartości z poprzedniego. Jest to dość nieefektywne rozwiązanie i zastanawiałem się czy nie da się tego zrobić w jakiś inny sposób.

PS. Zawartość headerów zmienia się w zależności od requesta. Każde zapytanie musi zostać obsłużone osobno.

3

Może zamiast pakować to w TL, mógłbyś utworzyć jakiś komponent, który byłby rejestrem klucz-wartość w pamięci. Powinno się dać ogarnąć na ConcurrentHashMapie, tylko pytanie jak to czyścić.

Jest jeszcze RequestScope, ale nigdy w praktyce nie korzystałem ;)

2
FunkyJimm napisał(a):

. Jest to dość nieefektywne rozwiązanie i zastanawiałem się czy nie da się tego zrobić w jakiś inny sposób.

ThreadLocal to coś co zawstydza nawet zmienną globalną. Wycieki to mały problem :-). Sprzątać się da dość łatwo.
Problem to przekazywanie argumentów funkcji na boku - idealne do wywalenia się przy kolejnej małej przeróbce kodu. Zwłaszcza jak zmiany będzie robił ktoś, kto nie wie, że te ThreadLocale tam sobie hulają.

1
FunkyJimm napisał(a):

To nie tak, że nie chce mi się tego przepisywać, po prostu za duża ingerencja w kod programu.

A wiesz, dla mnie "za duża ingerencja w kod programu" to taki ładniejszy sposób na powiedzenie "nie chce mi się tego przepisywać".

Ogólnie jeśli masz taką zwykłą obsługę requesta to masz dwie opcje:

  • albo wrzucisz to jako efekt uboczny obsługi requesta, i będzie to właśnie jakiś ThreadLocal, zmienna statyczna, zmienna z scope request czy cache
  • albo będziesz to wyciągał - przy czym nie musi to być w interceptorze, bo może być np. w serwisie - i przekazywał normalnie

Trzeciej drogi nie ma.

Tych serwisów mało nie jest a relacje pomiędzy nimi nie są tak oczywiste. Aktualnie po prostu odczytuje to w interceptorze wejściowym raz, wczytuje do ThreadLocal i wtedy jestem w stanie wyciągnąć to w serwisie, do którego docelowo ma to trafiać, tam muszę zrobić myk i przekazać to dalej do interceptora wyjściowego tworząc nowy obiekt ThreadLocal w nowym wątku, kopiując do niego wartości z poprzedniego. Jest to dość nieefektywne rozwiązanie i zastanawiałem się czy nie da się tego zrobić w jakiś inny sposób.

No właśnie dużo bardziej efektownym podejściem jest przekazywanie tego normalnie. Możesz spróbować napisać własnego wrappera do ServletRequest, który będzie zawierał interesujące cię pola - ale to raczej do zmiany w filtrze, a nie interceptorze. Nie będzie to zbyt eleganckie rozwiązanie, ale przynajmniej nie będziesz miał jakichś kwazistatycznych elementów.

PS. Zawartość headerów zmienia się w zależności od requesta. Każde zapytanie musi zostać obsłużone osobno.

Jeśli jednak dla danej wartości headera odpowiedź będzie taka sama to możesz zaprząc jakiś cache, i odwoływać się do niego. Będziesz miał większą kontrolę nad wspólną częścią, a wyrzucać zmienne możesz po jakimśtam czasie.

0

Robisz jakąś integrację, masz springa.. i nie używasz spring integration?

1 użytkowników online, w tym zalogowanych: 0, gości: 1