Flow: VaadinSession.getSession() is sometimes null in session destroy listeners

Created on 14 Jan 2021  路  6Comments  路  Source: vaadin/flow

Description of the bug / feature


In my usecase I want to keep track of all VaadinSessions. For that reason I am doing the following in the AppShell:

        serviceInitEvent.getSource().addSessionInitListener( ... );
        serviceInitEvent.getSource().addSessionDestroyListener(sessionDestroyEvent -> {
            if(sessionDestroyEvent != null && sessionDestroyEvent.getSession() != null) {
                //do smth with the sessionId
                               //this code is not reached
            }
        });

Expected behavior

I get the correct session when initialized, and I get the session before it is destroyed.

Actual behavior

The destroyed is session null.
Problem: I can not track which session was destroyed.

Versions:

- Vaadin / Flow version: 18.0.3
- Java version: 11
- OS version: Windows 10
- Application Server (if applicable): Spring Boot with Tomcat
Low Minor bug

All 6 comments

I couldn't reproduce the symptoms that you're describing. I created a new project with everything set as default from https://start.vaadin.com/ and made this addition to the Application class:

@Bean
public VaadinServiceInitListener serviceListener() {
    return new VaadinServiceInitListener() {
        @Override
        public void serviceInit(ServiceInitEvent serviceInitEvent) {
            System.out.println("Service init");
            serviceInitEvent.getSource().addSessionInitListener(sessionInitEvent -> {
                System.out.println("Session init: " + sessionInitEvent.getSession());
            });
            serviceInitEvent.getSource().addSessionDestroyListener(sessionDestroyEvent -> {
                System.out.println("Session destroy: " + sessionDestroyEvent.getSession());
            });
        }
    };
}

When the application is running, I can see log messages like these:

Session destroy: com.vaadin.flow.spring.SpringVaadinSession@3948cea6
Session init: com.vaadin.flow.spring.SpringVaadinSession@4a85427
Session destroy: com.vaadin.flow.spring.SpringVaadinSession@4a85427

I also looked at the framework code that creates those event objects and I couldn't find any code path that wouldn't have failed with a NullPointerException before an event without a session would have reached the listener.

Based on that, I have two potential things for you to look up:

  1. Maybe there's some other problem that means your listener is never called, rather than being called but going the wrong way in the if? You can find out that by adding the same kind of simple logging that I had in my test code.
  2. Maybe there's some add-on or such that introduces another code path than then one that I could find. Identifying that kind of case should be easy if you could share the stacktrace that leads to invoking the listener. You can find the stacktrace by adding this line to the beginning of the listener: new Exception().printStackTrace();.

Thanks for the fast reply @Legioth. I was also not able to reproduce this error local.

The error occurred in the production environment. I will log this error better and will reply if it appears again.

Here is an update on the error in production @Legioth:

Code:

//AppShell
    public void sessionDestroy(SessionDestroyEvent sessionDestroyEvent) {
        SessionManager.get().removeVaadinSession(sessionDestroyEvent.getSession());
    }

//SessionManager
    public void removeVaadinSession(VaadinSession vaadinSession) {
        try {
            sessionMap.remove(vaadinSession.getSession().getId());
        } catch (NullPointerException e) {
            log.error("vaadinsession session id null", e);
        }
    }

Error Message

2021-01-22 18:17:38.092 ERROR 1 --- [io-8080-exec-35] d.i.w.security.session.SessionManager    : vaadinsession session id null
java.lang.NullPointerException: null
    at de.izanami.webapp.security.session.SessionManager.removeVaadinSession(SessionManager.java:41) ~[classes!/:0.3.17]
    at de.izanami.webapp.security.AppShell.sessionDestroy(AppShell.java:63) ~[classes!/:0.3.17]
    at com.vaadin.flow.server.VaadinService.lambda$fireSessionDestroy$9c853e43$1(VaadinService.java:662) ~[flow-server-5.0.2.jar!/:5.0.2]
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) ~[na:na]
    at java.base/java.util.concurrent.FutureTask.run(Unknown Source) ~[na:na]
    at com.vaadin.flow.server.VaadinService.runPendingAccessTasks(VaadinService.java:2021) ~[flow-server-5.0.2.jar!/:5.0.2]
    at com.vaadin.flow.server.VaadinSession.unlock(VaadinSession.java:677) ~[flow-server-5.0.2.jar!/:5.0.2]
    at com.vaadin.flow.server.VaadinService.requestEnd(VaadinService.java:1478) ~[flow-server-5.0.2.jar!/:5.0.2]
    at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1553) ~[flow-server-5.0.2.jar!/:5.0.2]
    at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:238) ~[flow-server-5.0.2.jar!/:5.0.2]
    at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:110) ~[vaadin-spring-15.0.2.jar!/:na]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.39.jar!/:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:352) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.springframework.web.servlet.mvc.ServletForwardingController.handleRequestInternal(ServletForwardingController.java:141) ~[spring-webmvc-5.3.1.jar!/:5.3.1]
    at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:177) ~[spring-webmvc-5.3.1.jar!/:5.3.1]
    at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:52) ~[spring-webmvc-5.3.1.jar!/:5.3.1]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1061) ~[spring-webmvc-5.3.1.jar!/:5.3.1]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:961) ~[spring-webmvc-5.3.1.jar!/:5.3.1]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.1.jar!/:5.3.1]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.1.jar!/:5.3.1]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:652) ~[tomcat-embed-core-9.0.39.jar!/:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.1.jar!/:5.3.1]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.39.jar!/:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:105) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:101) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:92) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:218) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.4.1.jar!/:5.4.1]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93) ~[spring-boot-actuator-2.4.0.jar!/:2.4.0]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar!/:5.3.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:747) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.39.jar!/:9.0.39]
    at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]

I strongly suspect that in this case, it's not sessionDestroyEvent.getSession() that returns null, but rather vaadinSession.getSession().

The path that leads here is most likely like this:

  1. VaadinSession.valueUnbound() is run by the servlet container when the session is invalidated
  2. The thread that that performs the invalidation is not handling a Vaadin request, which takes us through the last else branch that just does service.fireSessionDestroy(this);
  3. The session is currently locked by some other thread which means that session.access(() -> ...) will leave the callback to fire the event in a queue to be run later.
  4. VaadinSession.valueUnbound() continues by doing session = null
  5. The thread that was locking the session finishes whatever it was doing and will process the callback in the queue as part of unlocking. This causes the destroy listeners to actually run.

If the session wouldn't be locked by another thread in step 3, then session.access would run the callback immediately which would lead to actually invoking the listeners before reaching session = null. This probably explains why this problem doesn't happen deterministically and is more likely to happen in an actively used production system.

There is a risk we cannot do anything about this. Changing the logic that fires the event to use accessSynchronously rather than the regular access when firing the event might have a risk of causing deadlocks. Changing valueUnbound() to not clear the session instance field has a risk of causing regressions since that session instance will throw IllegalStateException from most methods after invalidation (with getId() being one of the only exceptions). At the very least, we should more clearly document the invariants for this special case.

As a workaround, you could either use the VaadinSession instance itself as a key in sessionMap or alternatively extract the session id from the underlying session already in the session init listener and store it as an attribute using VaadinSession.setAttribute so that you can use getAttribute in the destroy listener to be able to get the id regardless of whether the session field has already been cleared. On the other hand, it might also be a feasible option to store whatever you're storing directly as an attribute in the session rather than using a separate map for it, but that does of course depend on exactly how you're using that map.

Thanks @Legioth for the detailed answer.

I can confirm that vaadinsession.getSession() is null.

I will go with the VaadinSession.setAttribute / getAttribute approach.

In https://github.com/vaadin/flow/issues/6959 it was suggested to use VaadinRequest.getCurrent().getWrappedSession() instead of event.getSession().getSession()

Was this page helpful?
0 / 5 - 0 ratings

Related issues

marcushellberg picture marcushellberg  路  4Comments

pleku picture pleku  路  4Comments

manolo picture manolo  路  3Comments

SomeoneToIgnore picture SomeoneToIgnore  路  4Comments

joheriks picture joheriks  路  4Comments