]> git.basschouten.com Git - openhab-addons.git/blob
281815b5c687741b4dfa1c9607156ca289398fe2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.lifx.internal.util;
14
15 import static org.openhab.binding.lifx.internal.LifxBindingConstants.PACKET_INTERVAL;
16
17 import java.util.List;
18 import java.util.Map;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.concurrent.CopyOnWriteArrayList;
21 import java.util.concurrent.locks.ReentrantLock;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.lifx.internal.fields.MACAddress;
26
27 /**
28  * The {@link LifxThrottlingUtil} is a utility class that regulates the frequency at which messages/packets are
29  * sent to LIFX lights. The LIFX LAN Protocol Specification states that lights can process up to 20 messages per second,
30  * not more.
31  *
32  * @author Karel Goderis - Initial contribution
33  * @author Wouter Born - Deadlock fix
34  */
35 @NonNullByDefault
36 public final class LifxThrottlingUtil {
37
38     private LifxThrottlingUtil() {
39         // hidden utility class constructor
40     }
41
42     /**
43      * Tracks when the last packet was sent to a LIFX light. The packet is sent after obtaining the lock and before
44      * releasing the lock.
45      */
46     private static class LifxLightCommunicationTracker {
47
48         private long timestamp;
49
50         private ReentrantLock lock = new ReentrantLock();
51
52         public void lock() {
53             lock.lock();
54         }
55
56         public void unlock() {
57             // When iterating over all trackers another thread may have inserted this object so this thread may not
58             // have a lock on it. When the thread does not have the lock, it also did not send a packet.
59             if (lock.isHeldByCurrentThread()) {
60                 timestamp = System.currentTimeMillis();
61                 lock.unlock();
62             }
63         }
64
65         public long getTimestamp() {
66             return timestamp;
67         }
68     }
69
70     /**
71      * A separate list of trackers is maintained when locking all lights in case of a broadcast. Iterators of
72      * {@link ConcurrentHashMap}s may behave non-linear when inserts take place to obtain more concurrency. When the
73      * iterator of {@code values()} of {@link #macTrackerMapping} is used for locking all lights, it could sometimes
74      * cause deadlock.
75      */
76     private static List<LifxLightCommunicationTracker> trackers = new CopyOnWriteArrayList<>();
77
78     private static Map<MACAddress, LifxLightCommunicationTracker> macTrackerMapping = new ConcurrentHashMap<>();
79
80     public static void lock(@Nullable MACAddress mac) throws InterruptedException {
81         if (mac != null) {
82             LifxLightCommunicationTracker tracker = getOrCreateTracker(mac);
83             tracker.lock();
84             waitForNextPacketInterval(tracker.getTimestamp());
85         } else {
86             lock();
87         }
88     }
89
90     private static LifxLightCommunicationTracker getOrCreateTracker(MACAddress mac) {
91         LifxLightCommunicationTracker tracker = macTrackerMapping.get(mac);
92         if (tracker == null) {
93             // for better performance only synchronize when necessary
94             synchronized (trackers) {
95                 // another thread may just have added a tracker in this synchronized block, so reevaluate
96                 tracker = macTrackerMapping.get(mac);
97                 if (tracker == null) {
98                     tracker = new LifxLightCommunicationTracker();
99                     trackers.add(tracker);
100                     macTrackerMapping.put(mac, tracker);
101                 }
102             }
103         }
104         return tracker;
105     }
106
107     private static void waitForNextPacketInterval(long timestamp) throws InterruptedException {
108         long timeToWait = Math.max(PACKET_INTERVAL - (System.currentTimeMillis() - timestamp), 0);
109         if (timeToWait > 0) {
110             Thread.sleep(timeToWait);
111         }
112     }
113
114     public static void unlock(@Nullable MACAddress mac) {
115         if (mac != null) {
116             LifxLightCommunicationTracker tracker = macTrackerMapping.get(mac);
117             if (tracker != null) {
118                 tracker.unlock();
119             }
120         } else {
121             unlock();
122         }
123     }
124
125     public static void lock() throws InterruptedException {
126         long lastStamp = 0;
127         for (LifxLightCommunicationTracker tracker : trackers) {
128             tracker.lock();
129             lastStamp = Math.max(lastStamp, tracker.getTimestamp());
130         }
131         waitForNextPacketInterval(lastStamp);
132     }
133
134     public static void unlock() {
135         for (LifxLightCommunicationTracker tracker : trackers) {
136             tracker.unlock();
137         }
138     }
139 }