]> git.basschouten.com Git - openhab-addons.git/blob
30d542842266b899258cab1032ff09a20bcea6a1
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * The {@link LifxThrottlingUtil} is a utility class that regulates the frequency at which messages/packets are
31  * sent to LIFX lights. The LIFX LAN Protocol Specification states that lights can process up to 20 messages per second,
32  * not more.
33  *
34  * @author Karel Goderis - Initial contribution
35  * @author Wouter Born - Deadlock fix
36  */
37 @NonNullByDefault
38 public final class LifxThrottlingUtil {
39
40     private static final Logger LOGGER = LoggerFactory.getLogger(LifxThrottlingUtil.class);
41
42     private LifxThrottlingUtil() {
43         // hidden utility class constructor
44     }
45
46     /**
47      * Tracks when the last packet was sent to a LIFX light. The packet is sent after obtaining the lock and before
48      * releasing the lock.
49      */
50     private static class LifxLightCommunicationTracker {
51
52         private long timestamp;
53
54         private ReentrantLock lock = new ReentrantLock();
55
56         public void lock() {
57             lock.lock();
58         }
59
60         public void unlock() {
61             // When iterating over all trackers another thread may have inserted this object so this thread may not
62             // have a lock on it. When the thread does not have the lock, it also did not send a packet.
63             if (lock.isHeldByCurrentThread()) {
64                 timestamp = System.currentTimeMillis();
65                 lock.unlock();
66             }
67         }
68
69         public long getTimestamp() {
70             return timestamp;
71         }
72     }
73
74     /**
75      * A separate list of trackers is maintained when locking all lights in case of a broadcast. Iterators of
76      * {@link ConcurrentHashMap}s may behave non-linear when inserts take place to obtain more concurrency. When the
77      * iterator of {@code values()} of {@link #macTrackerMapping} is used for locking all lights, it could sometimes
78      * cause deadlock.
79      */
80     private static List<LifxLightCommunicationTracker> trackers = new CopyOnWriteArrayList<>();
81
82     private static Map<MACAddress, LifxLightCommunicationTracker> macTrackerMapping = new ConcurrentHashMap<>();
83
84     public static void lock(@Nullable MACAddress mac) throws InterruptedException {
85         if (mac != null) {
86             LifxLightCommunicationTracker tracker = getOrCreateTracker(mac);
87             tracker.lock();
88             waitForNextPacketInterval(tracker.getTimestamp());
89         } else {
90             lock();
91         }
92     }
93
94     private static LifxLightCommunicationTracker getOrCreateTracker(MACAddress mac) {
95         LifxLightCommunicationTracker tracker = macTrackerMapping.get(mac);
96         if (tracker == null) {
97             // for better performance only synchronize when necessary
98             synchronized (trackers) {
99                 // another thread may just have added a tracker in this synchronized block, so reevaluate
100                 tracker = macTrackerMapping.get(mac);
101                 if (tracker == null) {
102                     tracker = new LifxLightCommunicationTracker();
103                     trackers.add(tracker);
104                     macTrackerMapping.put(mac, tracker);
105                 }
106             }
107         }
108         return tracker;
109     }
110
111     private static void waitForNextPacketInterval(long timestamp) throws InterruptedException {
112         long timeToWait = Math.max(PACKET_INTERVAL - (System.currentTimeMillis() - timestamp), 0);
113         if (timeToWait > 0) {
114             Thread.sleep(timeToWait);
115         }
116     }
117
118     public static void unlock(@Nullable MACAddress mac) {
119         if (mac != null) {
120             LifxLightCommunicationTracker tracker = macTrackerMapping.get(mac);
121             if (tracker != null) {
122                 tracker.unlock();
123             }
124         } else {
125             unlock();
126         }
127     }
128
129     public static void lock() throws InterruptedException {
130         long lastStamp = 0;
131         for (LifxLightCommunicationTracker tracker : trackers) {
132             tracker.lock();
133             lastStamp = Math.max(lastStamp, tracker.getTimestamp());
134         }
135         waitForNextPacketInterval(lastStamp);
136     }
137
138     public static void unlock() {
139         for (LifxLightCommunicationTracker tracker : trackers) {
140             tracker.unlock();
141         }
142     }
143 }