aboutsummaryrefslogtreecommitdiffstats
path: root/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionRepo.java
blob: 51fd610affdad769d9d3f9648b85417d2aac7da6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.session;

import com.yahoo.transaction.AbstractTransaction;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.transaction.Transaction;
import com.yahoo.vespa.config.server.TimeoutBudget;
import com.yahoo.vespa.config.server.NotFoundException;

import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

/**
 * A generic session repository that can store any type of session that extends the abstract interface.
 *
 * @author lulf
 * @since 5.1
 */
// TODO: This is a ZK cache. We should probably remove it, or make that explicit
public class SessionRepo<SESSIONTYPE extends Session> {

    private final HashMap<Long, SESSIONTYPE> sessions = new HashMap<>();

    public synchronized void addSession(SESSIONTYPE session) {
        internalAddSession(session);
    }

    /** Why is this needed? Because of implementation inheritance - see RemoteSessionRepo */
    protected synchronized final void internalAddSession(SESSIONTYPE session) {
        if (sessions.containsKey(session.getSessionId()))
            throw new IllegalArgumentException("There already exists a session with id '" + session.getSessionId() + "'");
        sessions.put(session.getSessionId(), session);
    }

    public synchronized void removeSessionOrThrow(long id) {
        internalRemoveSessionOrThrow(id);
    }

    /** Why is this needed? Because of implementation inheritance - see RemoteSessionRepo */
    protected synchronized final void internalRemoveSessionOrThrow(long id) {
        if ( ! sessions.containsKey(id))
            throw new IllegalArgumentException("No such session exists '" + id + "'");
        sessions.remove(id);
    }

    /** 
     * Removes a session in a transaction
     * 
     * @param id the id of the session to remove
     * @return the removed session, or null if none was found
     */
    public synchronized SESSIONTYPE removeSession(long id) { return sessions.remove(id); }
    
    public void removeSession(long id, NestedTransaction nestedTransaction) {
        SessionRepoTransaction transaction = new SessionRepoTransaction();
        transaction.addRemoveOperation(id);
        nestedTransaction.add(transaction);
    }

    /**
     * Gets a Session
     *
     * @param id session id
     * @return a session belonging to the id supplied, or null if no session with the id was found
     */
    public synchronized SESSIONTYPE getSession(long id) {
        return sessions.get(id);
    }

    /**
     * Gets a Session with a timeout
     *
     * @param id              session id
     * @param timeoutInMillis timeout for getting session (loops and wait for session to show up if not found)
     * @return a session belonging to the id supplied, or null if no session with the id was found
     */
    public synchronized SESSIONTYPE getSession(long id, long timeoutInMillis) {
        try {
            return internalGetSession(id, timeoutInMillis);
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while retrieving session with id " + id);
        }
    }

    private synchronized SESSIONTYPE internalGetSession(long id, long timeoutInMillis) throws InterruptedException {
        TimeoutBudget timeoutBudget = new TimeoutBudget(Clock.systemUTC(), Duration.ofMillis(timeoutInMillis));
        do {
            SESSIONTYPE session = getSession(id);
            if (session != null) {
                return session;
            }
            wait(100);
        } while (timeoutBudget.hasTimeLeft());
        throw new NotFoundException("Unable to retrieve session with id " + id + " before timeout was reached");
    }

    public synchronized Collection<SESSIONTYPE> listSessions() {
        return new ArrayList<>(sessions.values());
    }
    
    public class SessionRepoTransaction extends AbstractTransaction {

        public void addRemoveOperation(long sessionIdToRemove) {
            add(new RemoveOperation(sessionIdToRemove));
        }
        
        @Override
        public void prepare() { }

        @Override
        @SuppressWarnings("unchecked")
        public void commit() {
            for (Operation operation : operations())
                ((SessionOperation)operation).commit();
        }
        
        @Override
        @SuppressWarnings("unchecked")
        public void rollbackOrLog() {
            for (Operation operation : operations())
                ((SessionOperation)operation).rollback();
        }
        
        public abstract class SessionOperation implements Transaction.Operation {
            
            abstract void commit();
            
            abstract void rollback();
            
        }
        
        public class RemoveOperation extends SessionOperation {
            
            private final long sessionIdToRemove;
            private SESSIONTYPE removed = null;
            
            public RemoveOperation(long sessionIdToRemove) {
                this.sessionIdToRemove = sessionIdToRemove;
            }

            @Override
            public void commit() {
                removed = removeSession(sessionIdToRemove);
            }

            @Override
            public void rollback() {
                if (removed != null)
                    addSession(removed);
            }

        }

    }
    
}